hibernate/hibernate-orm · error · IllegalStateException
Session method called from entity lifecycle callback or…
Error message
Session method called from entity lifecycle callback or Interceptor method
What it means
While Hibernate invokes entity lifecycle callbacks (@PrePersist, @PostLoad, ...) and Interceptor methods it increments sessionUseProhibitedDepth; checkSessionReentrancy() then rejects any reentrant Session API call. JPA forbids EntityManager operations inside lifecycle listeners, and Hibernate enforces it to protect flush-cycle invariants — a session operation from inside a callback would recurse into the flush/persist machinery.
Solutions
- Move queries and saves out of the callback into the service/repository method that coordinates the operation.
- Inside callbacks, only mutate the entity's own fields or use injected collaborators (CDI/Spring support bean injection into entity listeners) instead of the session.
- If cross-entity work is unavoidable, collect it in the listener and perform it after the flush completes (e.g., @PostPersist plus outer service step), or use a dedicated mechanism (Hibernate event system, Envers, domain events).
Example fix
// before
public class AuditListener {
@PrePersist
void prePersist(Auditable a) {
a.setCreatedBy(em.find(User.class, currentUserId())); // IllegalStateException
}
}
// after
public class AuditListener {
@PrePersist
void prePersist(Auditable a) {
a.setCreatedBy(currentUserProvider().username()); // no session use
}
}
// cross-entity work moves to the service after save() Defensive patterns
Strategy: fallback
Validate before calling
// Pattern: never touch the session in a callback; defer work out of it
public class DeferredWorkListener {
private final Queue<Runnable> pending = new ConcurrentLinkedQueue<>();
@PreUpdate
void onUpdate(Auditable a) {
a.setUpdatedAt(Instant.now()); // allowed: own fields only
pending.add(() -> counterService.touch(a.getClass())); // no session use here
}
public void runPending() { Runnable r; while ((r = pending.poll()) != null) r.run(); } // call after flush, outside callback
} Prevention
- Treat lifecycle callbacks and Interceptor methods as session-free zones by design
- Resolve lookups (current user, defaults) via injected beans or ThreadLocal context, not session queries
- Run cross-entity side effects after the flush in the service layer or via domain events
When it happens
Trigger: An entity listener or Interceptor callback calls session/em methods: em.persist/find/query inside @PrePersist/@PostLoad/@PreUpdate, session.flush() or session.get() inside Interceptor.onSave/onFlushDirty/onLoad; also user event listeners calling back into the same session during a flush cycle.
Common situations: Audit listeners resolving the current user with a query; denormalization/counter updates cascading from callbacks; code ported from Hibernate 5 native Session patterns where reentrancy went undetected; @PostLoad enriching entities from other tables.
Related errors
- Flush during cascade is dangerous
- Callback method annotated '@
- Mapping for entity listener specified no callback methods
- You can only annotate one callback method per callback type…
- A '@JoinColumn' references a column named
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/0649a10c011c0d2a.
Report an issue: GitHub.
Appendix: source
Thrown at hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java:1135
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() {
checkOpen();
checkTransactionSyncStatus();
}
@Override
public void prepareForQueryExecution(boolean requiresTxn) {
checksBeforeQueryCreation();
if ( requiresTxn && !isTransactionInProgress() ) {
throw new TransactionRequiredException( "No active transaction" );
}
}
@Override
@NullableView on GitHub (pinned to fad1729dce)