apache/seatunnel · warning

unable to rollback prepared transaction, xid=

Error message

unable to rollback prepared transaction, xid={}

What it means

JdbcExactlyOnceSinkWriter.rollbackPrepareXidQuietly attempts to roll back a previously prepared XA transaction (prepareXid) and, because this path is best-effort during abort, it only logs a WARN when the XA rollback fails instead of throwing. The prepared transaction may then remain in-doubt on the database until XA recovery resolves it. The actual underlying exception is attached to the log entry.

Solutions

  1. Inspect the attached exception 'e' in the log line to identify the XA error code (e.g. XAER_RMFAIL vs XAER_NOTA)
  2. Rely on XA recovery: on restart the sink recovers pending transactions via xaGroupOps.recoverAndRollback, so ensure the DB user has XA_RECOVER_ADMIN / SELECT on xa_recover and the xidGenerator config is unchanged
  3. Check DB connectivity and reconnect before retrying abort
  4. Manually resolve in-doubt transactions if recovery cannot (e.g. xa recover; xa rollback <xid> in MySQL/Oracle)
  5. Keep xid generation parameters (subtask index, job id) stable across restarts so recovery can find the prepared xids

Example fix

// before: swallowing without cleanup of in-doubt tx
} catch (Exception e) {
    LOG.warn("unable to rollback prepared transaction, xid={}", xid, e);
}
// after: schedule recovery instead of silently dropping
} catch (Exception e) {
    LOG.warn("unable to rollback prepared transaction, xid={}", xid, e);
    xaGroupOps.recoverAndRollback(context, sinkcontext, xidGenerator, null);
}
Defensive patterns

Strategy: retry

Validate before calling

// before abort, check the facade/connection is still usable
if (xaFacade != null) {
    // verify DB reachable so rollback has a chance
    java.sql.Connection probe = dataSource.getConnection();
    probe.isValid(5);
    probe.close();
}

Try / catch

try {
    xaFacade.rollback(xid);
} catch (XAException e) {
    // XAER_NOTA (code -4): already rolled back/unknown — safe to ignore;
    // otherwise ensure the job restarts so XA recovery sweeps the in-doubt xid
    LOG.warn("rollback of {} failed, code={}", xid, e.errorCode, e);
}

Prevention

When it happens

Trigger: abortPrepare -> rollbackPrepareXidQuietly when xaFacade.rollback(xid) throws — e.g. connection to the database lost after prepare, the resource manager no longer knows the xid, XAER_RMFAIL, or the facade/connection is already closed.

Common situations: Task failure/abort during checkpoint after xa prepare; database restart between prepare and rollback; network partition to the DB; DBA killed the in-doubt transaction; connection pool reclaimed the XA connection.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/33a7164f8b3e2db0. Report an issue: GitHub.

Appendix: source

Thrown at seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/sink/JdbcExactlyOnceSinkWriter.java:259

        long candidate = txIdHint;
        if (candidate <= lastGeneratedTxId) {
            checkState(lastGeneratedTxId != Long.MAX_VALUE, "tx id exhausted");
            candidate = lastGeneratedTxId + 1;
        }
        lastGeneratedTxId = candidate;
        return candidate;
    }

    private void rollbackPrepareXidQuietly() {
        if (prepareXid == null || !xaFacade.isOpen()) {
            return;
        }
        Xid xid = prepareXid;
        try {
            LOG.debug("rollback prepared transaction, xid={}", xid);
            xaFacade.rollback(xid);
        } catch (Exception e) {
            LOG.warn("unable to rollback prepared transaction, xid={}", xid, e);
        } finally {
            prepareXid = null;
        }
    }

    private void rollbackPrepareXidOrThrow(Exception beginTxException) {
        if (prepareXid == null) {
            return;
        }
        Xid xid = prepareXid;
        if (!xaFacade.isOpen()) {
            throw new JdbcConnectorException(
                    JdbcConnectorErrorCode.XA_OPERATION_FAILED,
                    String.format(
                            "unable to rollback prepared transaction because xaFacade is closed, xid=%s",
                            xid),
                    beginTxException);
        }

View on GitHub (pinned to cf67b549a7)