PostgreSQL Branch Transaction Already Active (SQLSTATE 25002)

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 25Invalid 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

  1. XA driver or middleware sends a duplicate xa_start — A JDBC, ODBC, or other XA-capable driver calls xa_start(xid) on a connection that was never properly disassociated with xa_end(xid, TMSUCCESS) after a previous XA operation. This is the most common trigger in Java EE / Jakarta EE application servers using JTA.

  2. 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.

  3. Manual two-phase commit workflow error — A developer manually managing PREPARE TRANSACTION calls issues a new BEGIN or a second PREPARE TRANSACTION on a session that already holds a prepared transaction identifier, without having committed or rolled back the first.

  4. 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

  1. Always call xa_end before xa_start — In XA-managed environments, ensure every xa_start is paired with a corresponding xa_end (with TMSUCCESS or TMFAIL) 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;
    }
    
  2. Clean up orphaned prepared transactions at startup — Query pg_prepared_xacts to 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';
    
  3. 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 ROLLBACK on connection checkout to clear any stale transaction state:

    -- Use as a connection validation query that also resets state
    ROLLBACK;
    SELECT 1;
    
  4. Resume suspended XA branches instead of starting new ones — If your transaction coordinator suspended a branch, resume it with TMRESUME rather 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);
    
  5. Check pg_stat_activity for 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 25002 is defined in the SQL standard as part of class 25 (Invalid Transaction State). PostgreSQL implements this class faithfully — related codes in the same class include 25001 (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), and 25P01 (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/COMMIT will 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.PGXADataSource for XA support.
  • The PostgreSQL JDBC driver surfaces this as an XAException with error code XAER_PROTO wrapping the underlying PSQLException with SQLSTATE 25002.
  • Two-phase commit and XA support in PostgreSQL has been stable since version 8.0. The max_prepared_transactions configuration parameter (default 0 in many distributions) must be set to a non-zero value to use PREPARE TRANSACTION at all — a value of 0 disables 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.

Subscribe to the Pulse Newsletter

Get early access to new Pulse features, insightful blogs & exclusive events , webinars, and workshops.

We use cookies to provide an optimized user experience and understand our traffic. To learn more, read our use of cookies; otherwise, please choose 'Accept Cookies' to continue using our website.