hibernate/hibernate-orm · error · IllegalStateException
EntityManagerFactory is already closed
Error message
EntityManagerFactory is already closed
What it means
SessionFactoryImpl.close() is lenient by default: closing an already-closed factory only logs. However, when JPA closed-object compliance is enabled (hibernate.jpa.compliance.closed=true, or AvailableSettings.JPA_COMPLIANCE_CLOSED), it follows the JPA spec and throws IllegalStateException('EntityManagerFactory is already closed') on a second close. The status check happens under a lock and only Status.OPEN proceeds to real closing.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java:1062
* Closes the session factory, releasing all held resources.
*
* <ol>
* <li>cleans up used cache regions and "stops" the cache provider.
* <li>close the JDBC connection
* <li>remove the JNDI binding
* </ol>
*
* Note: Be aware that the sessionFactory instance still can
* be a "heavy" object memory wise after close() has been called. Thus
* it is important to not keep referencing the instance to let the garbage
* collector release the memory.
*/
@Override
public void close() {
synchronized (this) {
if ( status != Status.OPEN ) {
if ( getSessionFactoryOptions().getJpaCompliance().isJpaClosedComplianceEnabled() ) {
throw new IllegalStateException( "EntityManagerFactory is already closed" );
}
SESSION_FACTORY_LOGGER.alreadyClosed();
return;
}
status = Status.CLOSING;
}
final var preCloseException = preClose();
try {
SESSION_FACTORY_LOGGER.closingFactory( uuid );
observerChain.sessionFactoryClosing( this );
// NOTE: the null checks below handle cases where close is called
// from a failed attempt to create the SessionFactory
if ( cacheAccess != null ) {
cacheAccess.close();View on GitHub (pinned to fad1729dce)
Solutions
- Guard close calls: if (emf != null && emf.isOpen()) { emf.close(); }
- Designate a single owner for the factory's lifecycle (the Spring container, one bootstrap class) and let only it close
- In tests, close shared EMFs once in @AfterAll (or rely on the test context cache) rather than per-test
- As a last resort disable hibernate.jpa.compliance.closed — but prefer fixing the double close since JPA containers may rely on the strict behavior
Example fix
// before
@PreDestroy
public void shutdown() {
emf.close(); // second close when compliance enabled -> IllegalStateException
}
// after
@PreDestroy
public void shutdown() {
if (emf != null && emf.isOpen()) {
emf.close();
}
} Defensive patterns
Strategy: validation
Validate before calling
public static void closeQuietly(EntityManagerFactory emf) {
if (emf != null && emf.isOpen()) {
emf.close();
}
} Try / catch
try {
emf.close();
} catch (IllegalStateException e) {
// already closed (JPA closed compliance enabled) — safe to ignore
LOG.debug("EntityManagerFactory already closed", e);
} Prevention
- Establish single ownership of the factory lifecycle; document which component closes it
- Use isOpen() guards in every defensive close path (@PreDestroy, shutdown hooks)
- Decide deliberately whether hibernate.jpa.compliance.closed is on; if it is, treat double close as a bug to fix, not to suppress
When it happens
Trigger: Calling close() twice on the same SessionFactory/EntityManagerFactory with JPA closed compliance enabled. Happens with multiple owners closing the same factory (application shutdown hook plus container @PreDestroy, or shared static factory), or defensive close() calls in several components.
Common situations: Spring context shutdown racing an application shutdown hook that also closes the EMF; test teardown (@AfterEach) closing an EMF that a shared test-context already closed; enabling jpa closed compliance globally for strictness and then tripping over previously tolerated double closes.
Related errors
- EntityManager was already closed
- No child ServiceRegistry registrations found
- The ClassLoaderService cannot be reused (this instance was s
- Should not register strategies during shutdown
- BootstrapContext is no longer available
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/6d32aa66b9b19c1c.
Report an issue: GitHub.