flowable/flowable-engine · error · org.flowable.common.engine.api.FlowableException
Unexpected IllegalStateException while marking transaction…
Error message
Unexpected IllegalStateException while marking transaction rollback only
What it means
Flowable's JTA transaction context wraps calls to Transaction.setRollbackOnly() in rollback(). If the JTA Transaction manager throws an IllegalStateException (e.g. the transaction is in a state where marking rollback-only is not allowed, or no active transaction is associated with the current thread), it is rethrown as this FlowableException. It signals the JTA transaction cannot be marked for rollback as expected.
Solutions
- Check for concurrent transaction completion: ensure no other code (timeout, reaper, async thread) commits/rolls back the transaction while a Flowable command is in flight.
- Verify the transaction manager setup: the configured JtaTransactionManager/TransactionManager must manage transactions on the same thread that runs the Flowable command.
- If using Spring, ensure JtaTransactionContext is used consistently with JTA (SpringJtaTransactionManager / jta mode), not mixed with DataSourceTransactionManager semantics.
- Check for long-running commands exceeding the app server's transaction timeout; increase the timeout so the transaction is not reaped mid-command.
- Catch FlowableException in the command layer and retry the whole unit of work in a fresh transaction.
Example fix
// before: sharing one thread's transaction with async work
executor.submit(() -> processEngine.getRuntimeService().startProcessInstanceByKey("p"));
// after: run the command inside the JTA transaction (same thread)
transactionTemplate.execute(status ->
processEngine.getRuntimeService().startProcessInstanceByKey("p")); Defensive patterns
Strategy: try-catch
Validate before calling
Transaction tx = tm.getTransaction();
if (tx != null) {
int st = tx.getStatus();
if (st == Status.STATUS_ACTIVE || st == Status.STATUS_MARKED_ROLLBACK) {
// safe to proceed
}
} Type guard
boolean isActiveJtaTransaction(TransactionManager tm) {
try { return tm.getTransaction() != null && tm.getTransaction().getStatus() == Status.STATUS_ACTIVE; }
catch (SystemException e) { return false; }
} Try / catch
try {
runtimeService.startProcessInstanceByKey("p");
} catch (FlowableException e) {
if (e.getCause() instanceof IllegalStateException) {
// transaction lost/aborted: retry in a fresh transaction
transactionTemplate.execute(s -> runtimeService.startProcessInstanceByKey("p"));
} else throw e;
} Prevention
- Keep Flowable commands on threads owned by the JTA transaction manager.
- Avoid transaction timeouts: size the command work to fit within the TX timeout.
- Never commit/roll back the surrounding transaction from inside command logic or listeners.
- Keep the transaction framework choice consistent (JTA everywhere, or non-JTA everywhere).
When it happens
Trigger: Calling commandContext rollback when the JTA Transaction.getStatus() returned an active status but setRollbackOnly() then throws IllegalStateException — typically because the transaction was concurrently committed/rolled back or the thread lost its transaction association between the status check and the call.
Common situations: Container-managed transactions where another component completed the transaction mid-command; async/timeouts invalidating the transaction; mixing Flowable's JTA context with Spring @Transactional boundaries; application server quirks (e.g. transaction reaped by a reaper thread).
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
- IllegalStateException while registering synchronization
- RollbackException while registering synchronization
- SystemException while getting transaction
- SystemException while marking transaction rollback only
AI-assisted analysis of flowable/flowable-engine@d6d39ce1c6 (2026-09-11).
Data as JSON: /api/errors/2c2baac33ea4cd97.
Report an issue: GitHub.
Appendix: source
Thrown at modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/cfg/jta/JtaTransactionContext.java:56
this.transactionManager = transactionManager;
}
@Override
public void commit() {
// managed transaction, ignore
}
@Override
public void rollback() {
// managed transaction, mark rollback-only if not done so already.
try {
Transaction transaction = getTransaction();
int status = transaction.getStatus();
if (status != Status.STATUS_NO_TRANSACTION && status != Status.STATUS_ROLLEDBACK) {
transaction.setRollbackOnly();
}
} catch (IllegalStateException e) {
throw new FlowableException("Unexpected IllegalStateException while marking transaction rollback only", e);
} catch (SystemException e) {
throw new FlowableException("SystemException while marking transaction rollback only", e);
}
}
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();View on GitHub (pinned to d6d39ce1c6)