Implementing Discriminator, Schema, and Database Multi-Tenancy in Hibernate
Fast recall
Quick revision
Core answer
ThreadLocal set by a filter), and — for SCHEMA/DATABASE — a `MultiTenantConnectionProvider` that hands back a connection bound to the right schema or database. DISCRIMINATOR auto-appends WHERE tenant_id = ? (Hibernate 6.x). SCHEMA issues SET search_path per connection. DATABASE routes to a per-tenant pool. Choose by isolation requirement, tenant count, and cost: regulated/few -> DATABASE; many -> SCHEMA; massive -> DISCRIMINATOR with Postgres RLS as the safety net.Practical knowledge of Hibernate's multi-tenancy SPI: who supplies the tenant identity, who supplies the connection, and how filtering is enforced
Implementing `tenant_id` as plain `WHERE` in repositories — easily forgotten, leading to cross-tenant data leaks; also confusing tenant identity with security identity
Discuss `MultiTenantConnectionProvider` reset semantics, Postgres RLS as defense-in-depth for DISCRIMINATOR mode, and the operational realities of running migrations across hundreds of schemas or databases
Interview-ready guide
Interview answer
Hibernate has first-class multi-tenancy support. You configure one of three modes and plug in two interfaces.
The two plug points:
CurrentTenantIdentifierResolver— returns the active tenant from aThreadLocalMultiTenantConnectionProvider— supplies the connection bound to the right schema or database
The three modes: - DISCRIMINATOR — single DB, single schema, tenant_id column on every table; Hibernate 6 auto-filters with @TenantId.
- Postgres RLS adds defense-in-depth.
SCHEMA— single DB, multiple schemas; connection provider runsSET search_pathper acquisition.- Reset on release is mandatory.
DATABASE— one database per tenant; connection provider routes to per-tenant pool.
How to choose:
- Regulated industry, few tenants, max isolation ->
DATABASE - Mid-size SaaS, hundreds of tenants ->
SCHEMA - Massive scale, cost over physical isolation ->
DISCRIMINATORplus RLS
Operational realities:
- Set tenant identity in a filter; clear in
finally - Migrations multiply for
SCHEMAandDATABASE— automate per tenant on onboarding - Test with two tenants in the same JVM to catch leaks
Implementing Discriminator, Schema, and Database Multi-Tenancy in Hibernate
Direct Answer
Hibernate exposes three native multi-tenancy modes via the property hibernate.multiTenancy = DISCRIMINATOR, SCHEMA, or DATABASE. All three share the same plug points: a CurrentTenantIdentifierResolver that returns the active tenant per request (typically from ThreadLocal set by a filter), and — for SCHEMA/DATABASE — a MultiTenantConnectionProvider that hands back a connection bound to the right schema or database. DISCRIMINATOR auto-appends WHERE tenant_id = ? (Hibernate 6.x). SCHEMA issues SET search_path per connection. DATABASE routes to a per-tenant pool. Choose by isolation requirement, tenant count, and cost: regulated/few -> DATABASE; many -> SCHEMA; massive -> DISCRIMINATOR with Postgres RLS as the safety net.
Code Example
public class TenantContext {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<>();
public static void set(String tenantId) { CURRENT.set(tenantId); }
public static String get() { return CURRENT.get(); }
public static void clear() { CURRENT.remove(); }
}
public class TenantFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws ServletException, IOException {
String tenant = req.getHeader("X-Tenant-Id");
if (tenant == null || tenant.isBlank()) {
res.sendError(HttpServletResponse.SC_BAD_REQUEST, "Missing tenant");
return;
}
TenantContext.set(tenant);
try {
chain.doFilter(req, res);
} finally {
TenantContext.clear();
}
}
}
public class TenantIdResolver implements CurrentTenantIdentifierResolver {
@Override public String resolveCurrentTenantIdentifier() {
String t = TenantContext.get();
return t != null ? t : "public";
}
@Override public boolean validateExistingCurrentSessions() { return true; }
}
Register via Spring Boot:
spring:
jpa:
properties:
hibernate.multi_tenant_connection_provider: com.acme.MyConnectionProvider
hibernate.tenant_identifier_resolver: com.acme.TenantIdResolver
Common Mistakes & Pitfalls
Implementing tenant_id as plain WHERE in repositories — easily forgotten, leading to cross-tenant data leaks; also confusing tenant identity with security identity
Check your understanding
Answer these without looking back at the article.
- How does Hibernate's L2 cache behave under multi-tenancy — is it tenant-aware automatically?
- What happens if a request fails to set the tenant identifier — what does Hibernate do?
- How would you implement per-tenant rate limiting on top of the DISCRIMINATOR strategy?
- Can you migrate from DISCRIMINATOR to SCHEMA without downtime, and how?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.