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
- Register listeners earlier in the command lifecycle, before the JTA transaction reaches its committing phase.
- Check for listener code that itself completes or suspends the transaction (nested commits) and remove it.
- Increase the transaction timeout if commands routinely take long enough for the TX to be reaped before listener registration.
- Verify only one transaction framework controls the transaction (avoid double management by Spring AND container TM).
- 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
- Register listeners during command execution, not inside beforeCompletion synchronizations.
- Avoid nested commits/rollbacks inside listener or synchronization code.
- Keep commands within the TX timeout so the transaction stays active.
- Do not manage the same transaction with two frameworks.
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
- RollbackException while registering synchronization
- SystemException while registering synchronization
- IllegalStateException while registering synchronization
- RollbackException while registering synchronization
- SystemException while getting transaction
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)