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

  1. Treat the Session/EntityManager as unusable: catch the exception, close/discard the session, and create a fresh one — never reuse it
  2. Stop sharing a Session across threads: bind each EntityManager to the thread that began the transaction
  3. If a timeout reaper caused it, increase the transaction timeout so rollback happens on the owning thread
  4. Register your own javax.transaction.Synchronization to run cleanup on the completing thread instead of relying on delayed processing
  5. 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

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


AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22). Data as JSON: /api/errors/76f53994e4bd4088. Report an issue: GitHub.