flowable/flowable-engine · error · ActivitiException

RollbackException while registering synchronization

Error message

RollbackException while registering synchronization 

What it means

addTransactionListener() wraps javax.transaction.RollbackException from Transaction.registerSynchronization() in this ActivitiException. JTA throws RollbackException when the transaction has already been marked rollback-only, so no further synchronizations can be registered.

Solutions

  1. Find what marked the transaction rollback-only earlier in the command (the original failure log) — that is the real problem.
  2. Check transaction.getStatus() for STATUS_MARKED_ROLLBACK before registering listeners; skip registration when set.
  3. Move listener registration earlier in the command, before operations that can mark rollback-only.
  4. Inspect the chained RollbackException cause for the JTA provider's details.

Example fix

// before
transactionContext.addTransactionListener(TransactionState.COMMITTED, listener);
// after
if (transaction.getStatus() == Status.STATUS_ACTIVE) {
    transactionContext.addTransactionListener(TransactionState.COMMITTED, listener);
}
Defensive patterns

Strategy: validation

Validate before calling

boolean canRegister = transaction.getStatus() == Status.STATUS_ACTIVE; // STATUS_MARKED_ROLLBACK forbids synchronization

Try / catch

try { ctx.addTransactionListener(TransactionState.COMMITTED, listener); } catch (ActivitiException e) { if (e.getCause() instanceof RollbackException) { log.warn("transaction already marked rollback-only; listener skipped"); } else { throw e; } }

Prevention

When it happens

Trigger: Adding a transaction listener after an earlier operation in the same command marked the transaction rollback-only (failed command, business exception, setRollbackOnly).

Common situations: Engine command partially failed, then teardown or interceptor code tries to attach a COMMITTED-state listener; application code marked the transaction rollback before engine listeners were registered.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11). Data as JSON: /api/errors/36cee8137d639579. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable5-engine/src/main/java/org/activiti/engine/impl/cfg/jta/JtaTransactionContext.java:79

    protected Transaction getTransaction() {
        try {
            return transactionManager.getTransaction();
        } catch (SystemException e) {
            throw new ActivitiException("SystemException while getting transaction ", e);
        }
    }

    @Override
    public void addTransactionListener(TransactionState transactionState, final TransactionListener transactionListener) {
        Transaction transaction = getTransaction();
        CommandContext commandContext = Context.getCommandContext();
        try {
            transaction.registerSynchronization(new TransactionStateSynchronization(transactionState, transactionListener, commandContext));
        } catch (IllegalStateException e) {
            throw new ActivitiException("IllegalStateException while registering synchronization ", e);
        } catch (RollbackException e) {
            throw new ActivitiException("RollbackException while registering synchronization ", e);
        } catch (SystemException e) {
            throw new ActivitiException("SystemException while registering synchronization ", e);
        }
    }

    public static class TransactionStateSynchronization implements Synchronization {

        protected final TransactionListener transactionListener;
        protected final TransactionState transactionState;
        private final CommandContext commandContext;

        public TransactionStateSynchronization(TransactionState transactionState, TransactionListener transactionListener, CommandContext commandContext) {
            this.transactionState = transactionState;
            this.transactionListener = transactionListener;
            this.commandContext = commandContext;
        }

        @Override

View on GitHub (pinned to d6d39ce1c6)