{"record":{"id":"16a0a36823c29b23","repo":"flowable/flowable-engine","slug":"illegalstateexception-while-registering-synchroniz","errorCode":null,"errorMessage":"IllegalStateException while registering synchronization ","messagePattern":"IllegalStateException while registering synchronization ","errorType":"exception","errorClass":"org.flowable.common.engine.api.FlowableException","httpStatus":null,"severity":"error","filePath":"modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/cfg/jta/JtaTransactionContext.java","lineNumber":78,"sourceCode":"    }\n\n    protected Transaction getTransaction() {\n        try {\n            return transactionManager.getTransaction();\n        } catch (SystemException e) {\n            throw new FlowableException(\"SystemException while getting transaction \", e);\n        }\n    }\n\n    @Override\n    public void addTransactionListener(TransactionState transactionState, final TransactionListener transactionListener) {\n\n        Transaction transaction = getTransaction();\n        CommandContext commandContext = Context.getCommandContext();\n        try {\n            transaction.registerSynchronization(new TransactionStateSynchronization(transactionState, transactionListener, commandContext));\n        } catch (IllegalStateException e) {\n            throw new FlowableException(\"IllegalStateException while registering synchronization \", e);\n        } catch (RollbackException e) {\n            throw new FlowableException(\"RollbackException while registering synchronization \", e);\n        } catch (SystemException e) {\n            throw new FlowableException(\"SystemException while registering synchronization \", e);\n        }\n    }\n\n    public static class TransactionStateSynchronization implements Synchronization {\n\n        protected final TransactionListener transactionListener;\n        protected final TransactionState transactionState;\n        private final CommandContext commandContext;\n\n        public TransactionStateSynchronization(TransactionState transactionState, TransactionListener transactionListener, CommandContext commandContext) {\n            this.transactionState = transactionState;\n            this.transactionListener = transactionListener;\n            this.commandContext = commandContext;\n        }","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/flowable/flowable-engine/blob/d6d39ce1c69ff244f2d9dc6af756a9b95e865586/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/cfg/jta/JtaTransactionContext.java#L60-L96","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before: listener registered inside a beforeCommit synchronization (TX already preparing)\nsynchronizationRegistry.registerSynchronization(new Synchronization() {\n  public void beforeCompletion() {\n    commandContext.getTransactionContext().addTransactionListener(COMMITTED, listener);\n  }\n});\n// after: register the listener during command execution, before commit phase\ncommandContext.getTransactionContext().addTransactionListener(COMMITTED, listener);","handlingStrategy":"try-catch","validationCode":"int st = tx.getStatus();\nif (st != Status.STATUS_ACTIVE) {\n  throw new IllegalStateException(\"Cannot register synchronization, TX status=\" + st);\n}","typeGuard":"boolean canRegisterSynchronization(Transaction tx) {\n  try { return tx.getStatus() == Status.STATUS_ACTIVE; }\n  catch (SystemException e) { return false; }\n}","tryCatchPattern":"try {\n  commandContext.getTransactionContext().addTransactionListener(TransactionState.COMMITTED, listener);\n} catch (FlowableException e) {\n  if (e.getCause() instanceof IllegalStateException) {\n    listener.execute(CommandContextUtil.getCommandContext()); // run inline instead\n  } else throw e;\n}","preventionTips":["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."],"tags":["jta","transaction","synchronization","listener","flowable"],"backgroundTag":"invalid-state-transition","analyzedSha":"d6d39ce1c69ff244f2d9dc6af756a9b95e865586","analyzedAt":"2026-09-11T06:41:19.413Z","contentChangedAt":"2026-09-11T06:41:19.413Z","schemaVersion":2},"datasetVersion":"2026-09-18T11:17:12.947Z"}