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

  1. Guard close calls: if (emf != null && emf.isOpen()) { emf.close(); }
  2. Designate a single owner for the factory's lifecycle (the Spring container, one bootstrap class) and let only it close
  3. In tests, close shared EMFs once in @AfterAll (or rely on the test context cache) rather than per-test
  4. 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

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


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