hibernate/hibernate-orm · error · HibernateException
Transaction was rolled back in a different thread
Error message
Transaction was rolled back in a different thread
What it means
In JTA environments Hibernate registers a JTA Synchronization and tracks which thread owns the transaction/Session. When afterCompletion is signaled from a different thread, Hibernate cannot safely run it there, so it sets delayedCompletionHandling and defers. The next time the owning thread touches the coordinator, processAnyDelayedAfterCompletion() replays the after-completion work (doAfterCompletion, which resets state) and then throws this HibernateException to signal that the transaction finished on another thread.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/synchronization/SynchronizationCallbackCoordinatorTrackingImpl.java:87
doAfterCompletion( isCommitted( status ), false );
}
@Override
public void synchronizationRegistered() {
registrationThreadId = Thread.currentThread().getId();
}
@Override
public void processAnyDelayedAfterCompletion() {
if ( delayedCompletionHandling ) {
delayedCompletionHandling = false;
// false here (rather than how we used to keep and check the status) because as discussed above
// the delayed logic should only ever occur during rollback
doAfterCompletion( false, true );
// NOTE: doAfterCompletion calls reset
throw new HibernateException( "Transaction was rolled back in a different thread" );
}
}
}
View on GitHub (pinned to fad1729dce)
Solutions
- Treat the Session/EntityManager as unusable: catch the exception, close/discard the session, and create a fresh one — never reuse it
- Stop sharing a Session across threads: bind each EntityManager to the thread that began the transaction
- If a timeout reaper caused it, increase the transaction timeout so rollback happens on the owning thread
- Register your own javax.transaction.Synchronization to run cleanup on the completing thread instead of relying on delayed processing
- Upgrade to the latest Hibernate 6.x — thread tracking in SynchronizationCallbackCoordinatorTrackingImpl has had multiple fixes
Example fix
// before
// tx rolled back by reaper thread; reusing the same EntityManager
em.find(Order.class, id); // throws "Transaction was rolled back in a different thread"
// after
try {
em.find(Order.class, id);
}
catch (HibernateException e) {
if (e.getMessage() != null && e.getMessage().contains("different thread")) {
quietlyClose(em);
em = emf.createEntityManager(); // fresh persistence context
}
else throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// before any session use after an async/timeout-prone transaction
if (!session.isOpen()
|| session.getTransaction().getStatus() != TransactionStatus.ACTIVE) {
throw new IllegalStateException("session/transaction not usable; open a new one");
} Try / catch
try {
session.flush(); // or any operation
}
catch (HibernateException e) {
if (e.getMessage() != null && e.getMessage().contains("rolled back in a different thread")) {
closeQuietly(session);
session = sessionFactory.openSession(); // discard poisoned context
}
else {
throw e;
}
} Prevention
- Never share an EntityManager/Session between threads; keep it bound to the thread that started the transaction
- Set transaction timeouts generously enough that the transaction-reaper thread never aborts transactions for you
- Register cleanup as a javax.transaction.Synchronization so it runs on the completing thread
- After any caught timeout or async rollback, discard the session instead of reusing it
When it happens
Trigger: A JTA transaction is rolled back or completed by a non-owner thread — e.g. Narayana/Arjuna transaction-reaper timeout abort, asynchronous rollback in WildFly/WebSphere/Liberty, or handing the Session to an executor — and then the original thread performs any Session operation (find, flush, close, begin a new transaction) which drains the delayed completion.
Common situations: Transaction timeouts under Spring/EJB JTA where the reaper thread aborts the tx; sharing an EntityManager/Session across threads (CompletableFuture, @Async, custom executors); Quarkus/WildFly async request processing; test harnesses that roll back transactions on a background thread.
Related errors
- Could not access JTA Transaction to register synchronization
- Transaction is not accessible when using JTA with JPA-compli
- Exception pulsing TransactionCoordinator
- Explicitly joining a JTA transaction requires a JTA transact
- Unable to start isolated transaction
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/76f53994e4bd4088.
Report an issue: GitHub.