apache/seatunnel · warning
unable to fail/rollback transaction, xid={}: {}
Error message
unable to fail/rollback transaction, xid={}: {} What it means
A WARN log in XaGroupOpsImpl.failAndRollback() when rolling back a prepared transaction fails with a non-transient exception. Each xid is ended and rolled back (abort path); transient failures are logged at INFO and kept for retry, but generic Exceptions are logged at WARN and the transaction is marked failed. This typically happens during sink abort or checkpoint decline cleanup.
Source
Thrown at seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/xa/XaGroupOpsImpl.java:109
@Override
public GroupXaOperationResult<XidInfo> failAndRollback(Collection<XidInfo> xids) {
GroupXaOperationResult<XidInfo> result = new GroupXaOperationResult<>();
if (xids.isEmpty()) {
return result;
}
if (LOG.isDebugEnabled()) {
LOG.debug("rolling back {} transactions: {}", xids.size(), xids);
}
for (XidInfo x : xids) {
try {
xaFacade.failAndRollback(x.getXid());
result.succeeded(x);
} catch (XaFacade.TransientXaException e) {
LOG.info("unable to fail/rollback transaction, xid={}: {}", x, e.getMessage());
result.failedTransiently(x, e);
} catch (Exception e) {
LOG.warn("unable to fail/rollback transaction, xid={}: {}", x, e.getMessage());
result.failed(x, e);
}
}
if (!result.getForRetry().isEmpty()) {
LOG.info("failed to roll back {} transactions", result.getForRetry().size());
}
return result;
}
@Override
public void recoverAndRollback(
JobContext context,
SinkWriter.Context sinkContext,
XidGenerator xidGenerator,
Xid excludeXid) {
Collection<Xid> recovered =
xaFacade.recover().stream()
.map(View on GitHub (pinned to cf67b549a7)
Solutions
- Inspect the exception cause per xid in the logs; XAER_NOTA means it was already rolled back/committed — safe to ignore
- Restore DB connectivity and let recovery roll back remaining prepared transactions (they will be found via xa_recover)
- Check for another process concurrently recovering the same xids
- If rollback failures are persistent, manually resolve in-doubt transactions in the DB
Defensive patterns
Strategy: try-catch
Validate before calling
// Before abort cleanup, confirm DB reachability to reduce non-transient failures
try (Connection c = dataSource.getConnection()) { c.isValid(3); } Try / catch
try {
xaFacade.failAndRollback(xid);
} catch (XaFacade.TransientXaException e) {
LOG.info("transient, kept for retry: {}", xid);
} catch (Exception e) {
LOG.warn("unable to fail/rollback transaction, xid={}: {}", xid, e.getMessage());
} Prevention
- Distinguish XAER_NOTA (already resolved, benign) from real errors in logs
- Resolve in-doubt transactions manually if persistent
- Avoid aborting checkpoints while the DB is unreachable
When it happens
Trigger: For each buffered transaction, xaFacade.failAndRollback(x.getXid()) throws a non-XaFacade.TransientXaException Exception (e.g. connection dead, XA protocol error); the code logs 'unable to fail/rollback transaction, xid={}' and records result.failed(x, e).
Common situations: Aborting a checkpoint while the DB connection is broken; XAER_NOTA because the transaction was already resolved; driver errors during end(fail)/rollback.
Understand the failure class
Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.
Related errors
- XA_OPERATION_FAILED
- formatErrorMessage(action, xid, errorCode, errorMessage)
- rollback {} recovered transactions
- CLASS_NOT_FOUND
- CONNECT_DATABASE_FAILED
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/ef0d645508885045.
Report an issue: GitHub.