PostgreSQL raises ERROR: branch transaction already active with SQLSTATE 25002 and condition name branch_transaction_already_active when a distributed transaction manager attempts to start a new branch transaction on a connection that already has an active branch transaction in progress. This belongs to SQLSTATE class 25 — Invalid Transaction State.
What This Error Means
SQLSTATE class 25 covers errors related to invalid or unexpected transaction states. Code 25002 specifically signals a conflict in distributed or two-phase commit (2PC) transaction management: a transaction branch is already open on the current database session, and the caller is trying to open another one without first completing or rolling back the existing branch.
In PostgreSQL's two-phase commit protocol, a global transaction is split into branches — one per participating resource (database connection). Each branch goes through PREPARE TRANSACTION before the transaction manager issues the final COMMIT PREPARED or ROLLBACK PREPARED. SQLSTATE 25002 surfaces when an XA-aware driver or transaction coordinator sends a request to join or begin a new branch transaction (the XA start / join operation) on a connection that is already enlisted in a branch.
After this error is raised, the existing branch transaction remains open and in its current state. PostgreSQL does not automatically roll back the active branch. The connection is still usable, but the attempt to start a second branch is rejected — the caller must handle the error, roll back or complete the active branch, and then retry.
Common Causes
XA driver or middleware sends a duplicate
xa_start— A JDBC, ODBC, or other XA-capable driver callsxa_start(xid)on a connection that was never properly disassociated withxa_end(xid, TMSUCCESS)after a previous XA operation. This is the most common trigger in Java EE / Jakarta EE application servers using JTA.Connection pool returning an enlisted connection — A connection pooling layer (e.g., PgBouncer in transaction mode, or a JTA-aware pool like HikariCP with XA support) hands out a connection that still has an open XA branch from a previous transaction that ended abnormally (e.g., application crash mid-transaction). The new caller starts a fresh branch on a connection that was never cleaned up.
Manual two-phase commit workflow error — A developer manually managing
PREPARE TRANSACTIONcalls issues a newBEGINor a secondPREPARE TRANSACTIONon a session that already holds a prepared transaction identifier, without having committed or rolled back the first.Suspended XA branch not resumed before re-use — An XA transaction branch was suspended (
xa_end(xid, TMSUSPEND)) but the application logic tries to start an entirely new branch on the same connection instead of resuming the suspended one.
How to Fix branch_transaction_already_active
Always call
xa_endbeforexa_start— In XA-managed environments, ensure everyxa_startis paired with a correspondingxa_end(withTMSUCCESSorTMFAIL) before the connection is returned to a pool or reused:// Java XA example — ensure xa_end is always called xaResource.start(xid, XAResource.TMNOFLAGS); try { // perform work xaResource.end(xid, XAResource.TMSUCCESS); } catch (Exception e) { xaResource.end(xid, XAResource.TMFAIL); xaResource.rollback(xid); throw e; }Clean up orphaned prepared transactions at startup — Query
pg_prepared_xactsto find any leftover prepared transactions from a previous crash, and roll them back before the application begins accepting new work:-- Find all prepared transactions SELECT gid, prepared, owner, database FROM pg_prepared_xacts; -- Roll back an orphaned prepared transaction by its GID ROLLBACK PREPARED 'my-global-tx-id';Validate connection state before reuse in pools — Configure your connection pool to validate connections on borrow. For pools that support a test/validation query, use one that also checks for open transactions, or configure the pool to issue a
ROLLBACKon connection checkout to clear any stale transaction state:-- Use as a connection validation query that also resets state ROLLBACK; SELECT 1;Resume suspended XA branches instead of starting new ones — If your transaction coordinator suspended a branch, resume it with
TMRESUMErather than attempting to start a new branch on the same connection:// Resume a suspended branch, don't start a new one xaResource.start(xid, XAResource.TMRESUME);Check
pg_stat_activityfor stuck sessions — If the problem is systemic, identify connections holding open transactions:SELECT pid, usename, application_name, state, xact_start, query FROM pg_stat_activity WHERE state IN ('idle in transaction', 'idle in transaction (aborted)') ORDER BY xact_start;Then terminate the stuck sessions if appropriate:
SELECT pg_terminate_backend(pid);
Additional Information
- SQLSTATE
25002is defined in the SQL standard as part of class25(Invalid Transaction State). PostgreSQL implements this class faithfully — related codes in the same class include25001(active_sql_transaction),25003(inappropriate_access_mode_for_branch_transaction),25004(inappropriate_isolation_level_for_branch_transaction),25005(no_active_sql_transaction_for_branch_transaction),25006(read_only_sql_transaction), and25P01(no_active_sql_transaction) /25P02(in_failed_sql_transaction). - This error is almost exclusively seen in environments using XA/distributed transactions — standard single-database JDBC or psycopg2 applications using
BEGIN/COMMITwill not encounter it. - Java application servers (WildFly, GlassFish, WebLogic, WebSphere, Payara) with JTA/XA datasources are the most common context where this error appears. The PostgreSQL JDBC driver exposes
org.postgresql.xa.PGXADataSourcefor XA support. - The PostgreSQL JDBC driver surfaces this as an
XAExceptionwith error codeXAER_PROTOwrapping the underlyingPSQLExceptionwith SQLSTATE25002. - Two-phase commit and XA support in PostgreSQL has been stable since version 8.0. The
max_prepared_transactionsconfiguration parameter (default0in many distributions) must be set to a non-zero value to usePREPARE TRANSACTIONat all — a value of0disables 2PC entirely and will produce a different error.
Frequently Asked Questions
Why does this error only appear in production, not in development? Development environments typically use simple connection pools or direct connections without XA/JTA transaction management. Production application servers (WildFly, WebLogic, etc.) use full JTA with XA datasources to coordinate transactions across multiple resources. The error only manifests when true XA branch management is in play.
Does PostgreSQL support XA transactions natively?
Yes. PostgreSQL supports the XA protocol through its two-phase commit API (PREPARE TRANSACTION, COMMIT PREPARED, ROLLBACK PREPARED). The pg_prepared_xacts system view shows all currently prepared transactions. The PostgreSQL JDBC driver provides PGXADataSource and PGXAConnection for full XA compliance. Note that max_prepared_transactions must be greater than 0 in postgresql.conf for this to work.
Can I just ignore this error and retry?
No. The active branch transaction is still open and holding locks. Ignoring the error and retrying will not succeed until the active branch is either committed (COMMIT PREPARED) or rolled back (ROLLBACK PREPARED or xa_rollback). Failing to clean up orphaned branches consumes a prepared transaction slot and can exhaust max_prepared_transactions, blocking all further 2PC activity.
How do I prevent orphaned prepared transactions from accumulating?
Implement a recovery process that runs at application startup (or periodically) to query pg_prepared_xacts and roll back any transactions that are older than a safe threshold and not owned by a known active coordinator. Most XA-aware application servers perform this recovery automatically on startup, but crashes or misconfigurations can leave stragglers behind.