{"record":{"id":"876fc4a2dc257920","repo":"hibernate/hibernate-orm","slug":"reported-current-tenant-identifier-s-did-not-ma","errorCode":null,"errorMessage":"Reported current tenant identifier [%s] 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":56,"sourceCode":"\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":38,"sourceCodeEnd":65,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/context/spi/AbstractCurrentSessionContext.java#L38-L65","documentation":"Same validation as the null-mismatch variant, but here both the resolver's current tenant and the bound session's tenant are non-null and differ according to tenantIdentifierJavaType.areEqual(current, session). Hibernate throws TenantIdentifierMismatchException (a subclass of HibernateException) rather than silently handing out a session scoped to a different tenant.","triggerScenarios":"A real tenant switch hits an existing session: request for tenant B runs on a context still holding the session opened for tenant A — e.g. pooled/cached sessions, missing unbind on tenant change, or a shared current-session context across tenant-specific dispatch code.","commonSituations":"Multi-tenant web tiers where the tenant is resolved per request but the session context outlives it; background jobs iterating tenants while reusing getCurrentSession(); frontend bugs sending tenant A's token with tenant B's path; test suites switching tenants without resetting session bindings.","solutions":["Scope sessions to the tenant: close/unbind the old session when the tenant changes so a fresh one is created","Use a SessionFactory per tenant (or per-tenant session maps) when switching is frequent — cleaner than validation errors","Fix the caller: check why the tenant context changed mid-session (authentication/routing bug, leaked ThreadLocal)","Keep validateExistingCurrentSessions() = true in production; it is the guard against cross-tenant data leaks"],"exampleFix":"// before\n// thread still bound to tenant A's session; request is for tenant B\ntenantHolder.set(\"B\");\nSession s = sessionFactory.getCurrentSession(); // throws TenantIdentifierMismatchException\n\n// after\nif (ManagedSessionContext.hasBind(sessionFactory)) {\n    Session old = (Session) ManagedSessionContext.unbind(sessionFactory);\n    old.close(); // drop tenant A's session first\n}\ntenantHolder.set(\"B\");\nSession s = sessionFactory.getCurrentSession(); // fresh session for tenant B","handlingStrategy":"try-catch","validationCode":"String current = tenantResolver.resolveCurrentTenantIdentifier();\nif (sessionFactory.getCurrentSession().isTransactionInProgress()\n        && !Objects.equals(current, expectedTenantOfBoundSession)) {\n    unbindAndCloseCurrentSession(); // avoid mismatch before it throws\n}","typeGuard":null,"tryCatchPattern":"try {\n    return sessionFactory.getCurrentSession();\n} catch (org.hibernate.context.TenantIdentifierMismatchException e) {\n    Session old = (Session) org.hibernate.context.internal.ManagedSessionContext.unbind(sessionFactory);\n    if (old != null) old.close();\n    return sessionFactory.getCurrentSession(); // fresh session for the new tenant\n}","preventionTips":["Close/unbind sessions whenever the tenant changes; never reuse across tenants","Prefer per-tenant SessionFactory or per-tenant session maps for frequent switching","Keep validateExistingCurrentSessions() = true in production — it is the cross-tenant leak guard","Investigate routing/auth bugs whenever this fires; do not just catch it"],"tags":["hibernate","multitenancy","tenant-identifier","session-isolation","current-session"],"backgroundTag":"tenant-identifier-mismatch","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}