hibernate/hibernate-orm · error · IllegalStateException

Session/EntityManager is closed

Error message

Session/EntityManager is closed

What it means

checkOpen() runs at the head of nearly every Session/EntityManager operation. If the session is closed it marks the ongoing transaction rollback-only (when asked) and throws IllegalStateException. Closing is terminal: every later call on the same session fails identically. This is the standard guard behind 'using a closed EntityManager' failures.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java:1121

	protected void cleanupOnClose() {
		// nothing to do in base impl, here for SessionImpl hook
	}

	@Override
	public boolean isOpenOrWaitingForAutoClose() {
		return !closed && factory.isOpen()
			|| waitingForAutoClose;
	}

	@Override
	public void checkOpen(boolean markForRollbackIfClosed) {
		checkSessionReentrancy();
		if ( isClosed() ) {
			if ( markForRollbackIfClosed && transactionCoordinator.isTransactionActive() ) {
				markForRollbackOnly();
			}
			throw new IllegalStateException( "Session/EntityManager is closed" );
		}
	}

	private void startSessionUseProhibited() {
		sessionUseProhibitedDepth++;
	}

	private void finishSessionUseProhibited() {
		sessionUseProhibitedDepth--;
	}

	protected void checkSessionReentrancy() {
		if ( sessionUseProhibitedDepth > 0 ) {
			throw new IllegalStateException( "Session method called from entity lifecycle callback or Interceptor method" );
		}
	}

	protected void checksBeforeQueryCreation() {

View on GitHub (pinned to fad1729dce)

Solutions

  1. Keep all entity access, including lazy navigation, inside the transactional scope that loaded the entities.
  2. Fetch what you need up front (join fetch, EntityGraph, @Fetch) so no session is required after the boundary.
  3. For later processing, pass IDs and reload in a fresh session/transaction instead of reusing the closed one.
  4. If you must continue with loaded instances, open a new session and reattach via session.merge()/lock().

Example fix

// before
List<Order> orders;
try (Session s = sf.openSession()) {
    orders = s.createQuery("from Order", Order.class).list();
}
orders.get(0).getItems().size(); // IllegalStateException: session closed
// after
List<Order> orders;
try (Session s = sf.openSession()) {
    orders = s.createQuery("from Order o join fetch o.items", Order.class).list();
} // data already loaded, safe outside
Defensive patterns

Strategy: validation

Validate before calling

if (!session.isOpen()) {
    session = sessionFactory.openSession(); // or fail explicitly
}
return session.find(Customer.class, id);

Try / catch

try {
    return session.find(Customer.class, id);
} catch (IllegalStateException e) {
    if (!session.isOpen()) {
        try (Session fresh = sessionFactory.openSession()) {
            return fresh.find(Customer.class, id);
        }
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling any session/em method after session.close(); using a container-managed EntityManager after the @Transactional method it belonged to exited; reusing a request-scoped EM in a later thread; keeping an EntityManager in a field/cache across HTTP requests.

Common situations: Lazy loading after the transaction closed (sibling of LazyInitializationException); try-with-resources closing the session before async/later code uses loaded objects; servlet filters closing sessions too early; retry loops reusing the same closed EM; background jobs receiving detached entities.

Related errors


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