{"record":{"id":"1f8b19165ea78201","repo":"hibernate/hibernate-orm","slug":"reported-current-tenant-identifier-did-not-match-t","errorCode":null,"errorMessage":"Reported current tenant identifier did not match tenant identifier from existing session [%s]","messagePattern":"Reported current tenant identifier did not match tenant identifier from existing session \\[(.+?)\\]","errorType":"exception","errorClass":"TenantIdentifierMismatchException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/context/spi/AbstractCurrentSessionContext.java","lineNumber":50,"sourceCode":"\n\tprotected SessionBuilder baseSessionBuilder() {\n\t\tfinal var builder = factory.withOptions();\n\t\tfinal var resolver = factory.getCurrentTenantIdentifierResolver();\n\t\tif ( resolver != null ) {\n\t\t\tbuilder.tenantIdentifier( resolver.resolveCurrentTenantIdentifier() );\n\t\t}\n\t\treturn builder;\n\t}\n\n\tprotected void validateExistingSession(Session existingSession) {\n\t\tfinal var resolver = factory.getCurrentTenantIdentifierResolver();\n\t\tif ( resolver != null && resolver.validateExistingCurrentSessions() ) {\n\t\t\tfinal Object currentValue = resolver.resolveCurrentTenantIdentifier();\n\t\t\tfinal var tenantIdentifierJavaType = factory.getTenantIdentifierJavaType();\n\t\t\tfinal Object tenantIdentifierValue = existingSession.getTenantIdentifierValue();\n\t\t\tif ( tenantIdentifierValue == null || currentValue == null ) {\n\t\t\t\tif ( tenantIdentifierValue != currentValue ) {\n\t\t\t\t\tthrow new TenantIdentifierMismatchException(\n\t\t\t\t\t\t\t\"Reported current tenant identifier did not match tenant identifier from existing session [%s]\"\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t\telse if ( !tenantIdentifierJavaType.areEqual( currentValue, tenantIdentifierValue ) ) {\n\t\t\t\tthrow new TenantIdentifierMismatchException(\n\t\t\t\t\t\t\"Reported current tenant identifier [%s] did not match tenant identifier from existing session [%s]\"\n\t\t\t\t\t\t\t\t.formatted( tenantIdentifierJavaType.toString( currentValue ),\n\t\t\t\t\t\t\t\t\t\ttenantIdentifierJavaType.toString( tenantIdentifierValue ) )\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n}\n","sourceCodeStart":32,"sourceCodeEnd":65,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/context/spi/AbstractCurrentSessionContext.java#L32-L65","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\npublic class TenantResolver implements CurrentTenantIdentifierResolver<String> {\n    public String resolveCurrentTenantIdentifier() {\n        return tenantHolder.get(); // null on async thread\n    }\n    public boolean validateExistingCurrentSessions() { return true; }\n}\n\n// after: propagate tenant before session access\ntenantHolder.set(requestTenant);\nSession s = sessionFactory.getCurrentSession();","handlingStrategy":"validation","validationCode":"String tenant = tenantHolder.get();\nif (tenant == null) {\n    throw new IllegalStateException(\"Tenant context missing; set it before accessing the current session\");\n}\nSession s = sessionFactory.getCurrentSession();","typeGuard":null,"tryCatchPattern":"try {\n    return sessionFactory.getCurrentSession();\n} catch (org.hibernate.context.TenantIdentifierMismatchException e) {\n    // one side was null: tenant context lost or session created before tenant was known\n    unbindAndCloseCurrentSession();\n    throw new IllegalStateException(\"Tenant context inconsistent with bound session; re-establish tenant and session\", e);\n}","preventionTips":["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"],"tags":["hibernate","multitenancy","tenant-identifier","current-session","thread-local"],"backgroundTag":"tenant-identifier-mismatch","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}