flowable/flowable-engine · error · org.flowable.common.engine.api.FlowableException

IllegalStateException while registering synchronization

Error message

IllegalStateException while registering synchronization 

What it means

JtaTransactionContext.addTransactionListener() registers a Synchronization with the active JTA Transaction; an IllegalStateException from registerSynchronization is wrapped as this FlowableException. Per JTA spec this happens when the transaction is no longer active (e.g. already prepared/committed/rolled back), so the listener cannot be attached.

Solutions

  1. Register listeners earlier in the command lifecycle, before the JTA transaction reaches its committing phase.
  2. Check for listener code that itself completes or suspends the transaction (nested commits) and remove it.
  3. Increase the transaction timeout if commands routinely take long enough for the TX to be reaped before listener registration.
  4. Verify only one transaction framework controls the transaction (avoid double management by Spring AND container TM).
  5. Catch FlowableException where listeners are registered and degrade gracefully (execute listener logic inline instead of via synchronization).

Example fix

// before: listener registered inside a beforeCommit synchronization (TX already preparing)
synchronizationRegistry.registerSynchronization(new Synchronization() {
  public void beforeCompletion() {
    commandContext.getTransactionContext().addTransactionListener(COMMITTED, listener);
  }
});
// after: register the listener during command execution, before commit phase
commandContext.getTransactionContext().addTransactionListener(COMMITTED, listener);
Defensive patterns

Strategy: try-catch

Validate before calling

int st = tx.getStatus();
if (st != Status.STATUS_ACTIVE) {
  throw new IllegalStateException("Cannot register synchronization, TX status=" + st);
}

Type guard

boolean canRegisterSynchronization(Transaction tx) {
  try { return tx.getStatus() == Status.STATUS_ACTIVE; }
  catch (SystemException e) { return false; }
}

Try / catch

try {
  commandContext.getTransactionContext().addTransactionListener(TransactionState.COMMITTED, listener);
} catch (FlowableException e) {
  if (e.getCause() instanceof IllegalStateException) {
    listener.execute(CommandContextUtil.getCommandContext()); // run inline instead
  } else throw e;
}

Prevention

When it happens

Trigger: Registering a transaction listener (commandContext transaction listeners, e.g. fired on COMMITTED/ROLLED_BACK state) when the JTA transaction is in a state that disallows new synchronizations — typically after prepare(), or on an inactive/completed transaction.

Common situations: Command completion code (session close, listeners) running after the container already committed; late listener registration during beforeCommit synchronizations that trigger nested engine work; transaction timing out and being rolled back just before registration.

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/16a0a36823c29b23. Report an issue: GitHub.

Appendix: source

Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/cfg/jta/JtaTransactionContext.java:78

    }

    protected Transaction getTransaction() {
        try {
            return transactionManager.getTransaction();
        } catch (SystemException e) {
            throw new FlowableException("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 FlowableException("IllegalStateException while registering synchronization ", e);
        } catch (RollbackException e) {
            throw new FlowableException("RollbackException while registering synchronization ", e);
        } catch (SystemException e) {
            throw new FlowableException("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;
        }

View on GitHub (pinned to d6d39ce1c6)