hibernate/hibernate-orm · error · TenantIdentifierMismatchException

Reported current tenant identifier did not match tenant iden

Error message

Reported current tenant identifier did not match tenant identifier from existing session [%s]

What it means

When a CurrentTenantIdentifierResolver is configured and its validateExistingCurrentSessions() returns true, every getCurrentSession() re-resolves the current tenant and compares it with the tenant of the session already bound to the context. This variant fires when exactly one side is null (tenant set on the session but the resolver reports none, or vice versa): null and non-null never match because plain identity comparison is used.

Source

Thrown at hibernate-core/src/main/java/org/hibernate/context/spi/AbstractCurrentSessionContext.java:50

	protected SessionBuilder baseSessionBuilder() {
		final var builder = factory.withOptions();
		final var resolver = factory.getCurrentTenantIdentifierResolver();
		if ( resolver != null ) {
			builder.tenantIdentifier( resolver.resolveCurrentTenantIdentifier() );
		}
		return builder;
	}

	protected void validateExistingSession(Session existingSession) {
		final var resolver = factory.getCurrentTenantIdentifierResolver();
		if ( resolver != null && resolver.validateExistingCurrentSessions() ) {
			final Object currentValue = resolver.resolveCurrentTenantIdentifier();
			final var tenantIdentifierJavaType = factory.getTenantIdentifierJavaType();
			final Object tenantIdentifierValue = existingSession.getTenantIdentifierValue();
			if ( tenantIdentifierValue == null || currentValue == null ) {
				if ( tenantIdentifierValue != currentValue ) {
					throw new TenantIdentifierMismatchException(
							"Reported current tenant identifier did not match tenant identifier from existing session [%s]"
					);
				}
			}
			else if ( !tenantIdentifierJavaType.areEqual( currentValue, tenantIdentifierValue ) ) {
				throw new TenantIdentifierMismatchException(
						"Reported current tenant identifier [%s] did not match tenant identifier from existing session [%s]"
								.formatted( tenantIdentifierJavaType.toString( currentValue ),
										tenantIdentifierJavaType.toString( tenantIdentifierValue ) )
				);
			}
		}
	}
}

View on GitHub (pinned to fad1729dce)

Solutions

  1. Establish the tenant context before any session is opened or looked up, and keep it stable for the whole session lifetime
  2. Propagate the tenant context explicitly to async threads (wrap tasks with tenant capture/restore)
  3. When the tenant legitimately changes, close/unbind the old session instead of reusing the bound one
  4. Only disable validation (validateExistingCurrentSessions() = false) if you take over guaranteeing tenant isolation yourself

Example fix

// before
public class TenantResolver implements CurrentTenantIdentifierResolver<String> {
    public String resolveCurrentTenantIdentifier() {
        return tenantHolder.get(); // null on async thread
    }
    public boolean validateExistingCurrentSessions() { return true; }
}

// after: propagate tenant before session access
tenantHolder.set(requestTenant);
Session s = sessionFactory.getCurrentSession();
Defensive patterns

Strategy: validation

Validate before calling

String tenant = tenantHolder.get();
if (tenant == null) {
    throw new IllegalStateException("Tenant context missing; set it before accessing the current session");
}
Session s = sessionFactory.getCurrentSession();

Try / catch

try {
    return sessionFactory.getCurrentSession();
} catch (org.hibernate.context.TenantIdentifierMismatchException e) {
    // one side was null: tenant context lost or session created before tenant was known
    unbindAndCloseCurrentSession();
    throw new IllegalStateException("Tenant context inconsistent with bound session; re-establish tenant and session", e);
}

Prevention

When it happens

Trigger: A session was opened with a tenant identifier but the resolver now reports null (tenant context ThreadLocal cleared or never set on this thread), or the resolver reports a tenant while the bound session was created without one (opened before the tenant context was established).

Common situations: Tenant context held in a ThreadLocal that is not propagated to executor/async threads or is cleared by a filter before late session access; tenant resolution happening after session binding in request lifecycle; mid-refactor mixes of tenant-aware and non-tenant sessions.

Related errors


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