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
- Establish the tenant context before any session is opened or looked up, and keep it stable for the whole session lifetime
- Propagate the tenant context explicitly to async threads (wrap tasks with tenant capture/restore)
- When the tenant legitimately changes, close/unbind the old session instead of reusing the bound one
- 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
- Set the tenant context before opening/binding sessions and keep it for the whole session lifetime
- Propagate tenant ThreadLocals explicitly to executors/async tasks
- Clear tenant context only after the session is unbound and closed
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
- Reported current tenant identifier [%s] did not match tenant
- Could not obtain TransactionManager from JtaPlatform
- Unable to register cleanup Synchronization with TransactionM
- Unable to locate current JTA transaction
- Current transaction is not in progress
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/1f8b19165ea78201.
Report an issue: GitHub.