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
- Keep all entity access, including lazy navigation, inside the transactional scope that loaded the entities.
- Fetch what you need up front (join fetch, EntityGraph, @Fetch) so no session is required after the boundary.
- For later processing, pass IDs and reload in a fresh session/transaction instead of reusing the closed one.
- 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
- Never store EntityManager/Session in instance fields across requests
- Do lazy navigation and fetching inside the transactional boundary, or join-fetch up front
- Use try-with-resources scoped tightly around the code that needs the session
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
- Cannot lazily initialize collection
- Cannot begin Transaction on closed Session/EntityManager
- EntityManager was already closed
- {} is closed
- Logical connection is closed
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/934b90658fd7fa15.
Report an issue: GitHub.