{"record":{"id":"7c1a531be3498883","repo":"hibernate/hibernate-orm","slug":"sessionfactory-configured-for-multi-tenancy-but-n","errorCode":null,"errorMessage":"SessionFactory configured for multi-tenancy, but no tenant identifier specified","messagePattern":"SessionFactory configured for multi-tenancy, but no tenant identifier specified","errorType":"exception","errorClass":"HibernateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java","lineNumber":801,"sourceCode":"\t}\n\n\t@Override\n\t@Nonnull\n\tpublic SharedSessionBuilder sessionWithOptions() {\n\t\tcheckSessionReentrancy();\n\t\treturn new SharedSessionBuilderImpl( this ) {\n\t\t\t@Override\n\t\t\tprotected SessionImplementor createSession(SharedStatefulOptions options) {\n\t\t\t\treturn new SessionImpl( factory, options );\n\t\t\t}\n\t\t};\n\t}\n\n\tprotected final void setUpMultitenancy(SessionFactoryImplementor factory, LoadQueryInfluencers loadQueryInfluencers) {\n\t\tif ( factory.getDefinedFilterNames().contains( TenantIdBinder.FILTER_NAME ) ) {\n\t\t\tfinal Object tenantIdentifier = getTenantIdentifierValue();\n\t\t\tif ( tenantIdentifier == null ) {\n\t\t\t\tthrow new HibernateException( \"SessionFactory configured for multi-tenancy, but no tenant identifier specified\" );\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfinal var resolver = factory.getCurrentTenantIdentifierResolver();\n\t\t\t\tif ( resolver==null || !resolver.isRoot( tenantIdentifier ) ) {\n\t\t\t\t\t// turn on the filter, unless this is the \"root\" tenant with access to all partitions\n\t\t\t\t\tloadQueryInfluencers.enableFilter( TenantIdBinder.FILTER_NAME )\n\t\t\t\t\t\t\t.setParameter( TenantIdBinder.PARAMETER_NAME, tenantIdentifier );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate void logInconsistentOptions(SharedSessionCreationOptions sharedOptions) {\n\t\t// TODO: these should probable be exceptions!\n\t\tif ( sharedOptions.shouldAutoJoinTransactions() ) {\n\t\t\tSESSION_LOGGER.invalidAutoJoinTransactionsWithSharedConnection();\n\t\t}\n\t\tif ( sharedOptions.getPhysicalConnectionHandlingMode() != connectionHandlingMode ) {","sourceCodeStart":783,"sourceCodeEnd":819,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java#L783-L819","documentation":"Thrown while a Session initializes: the mapping defines tenant discrimination via @TenantId, which Hibernate implements as a session filter (TenantIdBinder.FILTER_NAME), so every session must carry a tenant identifier. setUpMultitenancy() found getTenantIdentifierValue() null, meaning neither the caller (SessionCreationOptions) nor the CurrentTenantIdentifierResolver supplied a tenant. Without it the tenant filter cannot be parameterized and cross-tenant leakage would occur, so Hibernate refuses to start the session.","triggerScenarios":"An entity carries @TenantId (or the TenantIdBinder filter is otherwise registered) and a session is opened without a tenant: sessionFactory.openSession(), sessionFactory.withOptions().openSession(), or a CurrentTenantIdentifierResolver that returns null for the current context (empty ThreadLocal/SecurityContext, non-web thread).","commonSituations":"Adding @TenantId to entities in an existing app that opens sessions directly; background jobs, schedulers, and tests running outside the request-scoped tenant context; a Spring tenant-context filter or resolver bean not applied to async/@Scheduled threads; resolver registered too late or not wired into LocalContainerEntityManagerFactoryBean/SessionFactoryBuilder.","solutions":["Open every tenant-aware session with an explicit tenant: sessionFactory.withOptions().tenantIdentifier(tenantId).openSession().","Register a CurrentTenantIdentifierResolver (e.g., returning the tenant from a ThreadLocal or SecurityContext) with the SessionFactory/EntityManagerFactory and make sure it never returns null on paths that open sessions.","For JPA bootstrap, pass the tenant at EntityManager creation: emf.createEntityManager(Map.of(AvailableSettings.TENANT_IDENTIFIER, tenantId)).","In jobs and tests, wrap the body with tenantContext.set(...) (or pass the tenant explicitly) so resolver-based resolution always finds a value."],"exampleFix":"// before\ntry (Session session = sessionFactory.openSession()) { // throws: @TenantId mapped, no tenant\n    session.createQuery(\"from Customer\", Customer.class).list();\n}\n// after\ntry (Session session = sessionFactory.withOptions().tenantIdentifier(currentTenant()).openSession()) {\n    session.createQuery(\"from Customer\", Customer.class).list();\n}","handlingStrategy":"validation","validationCode":"// Guard every tenant-aware session open behind one helper\npublic static Session openTenantSession(SessionFactory sf, TenantContext ctx) {\n    Object tenant = ctx.get();\n    if (tenant == null) {\n        throw new IllegalStateException(\"Tenant context missing; refusing to open session\");\n    }\n    return sf.withOptions().tenantIdentifier(tenant).openSession();\n}","typeGuard":null,"tryCatchPattern":"try (Session s = sf.openSession()) { ... }\ncatch (HibernateException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"no tenant identifier specified\")) {\n        throw new TenantContextMissingException(\"Configure tenant before session use\", e);\n    }\n    throw e;\n}","preventionTips":["Route all session opening through one factory method that injects the tenant","Make CurrentTenantIdentifierResolver throw a descriptive error itself when context is empty instead of returning null","Set up tenant context in schedulers, async executors, and test setups, not just web filters"],"tags":["hibernate","multi-tenancy","tenant-id","session","orm"],"backgroundTag":"missing-tenant-identifier","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}