java 56 lines · 7 steps

Multi-tenant row filtering with Hibernate in Spring

Enforce per-tenant data isolation automatically using a Hibernate filter toggled by a Spring interceptor on every request.

Explained by highlit
1@Entity
2@Table(name = "invoices")
3@FilterDef(name = "tenantFilter", parameters = @ParamDef(name = "tenantId", type = String.class))
4@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
5public class Invoice {
6 
7 @Id
8 @GeneratedValue(strategy = GenerationType.UUID)
9 private UUID id;
10 
11 @Column(name = "tenant_id", nullable = false, updatable = false)
12 private String tenantId;
13 
14 @Column(nullable = false)
15 private BigDecimal amount;
16 
17 @Enumerated(EnumType.STRING)
18 private InvoiceStatus status;
19}
20 
21@Component
22@RequiredArgsConstructor
23public class TenantFilterInterceptor implements HandlerInterceptor {
24 
25 private final EntityManager entityManager;
26 
27 @Override
28 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
29 String tenantId = TenantContext.getTenantId();
30 if (tenantId == null) {
31 response.setStatus(HttpStatus.FORBIDDEN.value());
32 return false;
33 }
34 entityManager.unwrap(Session.class)
35 .enableFilter("tenantFilter")
36 .setParameter("tenantId", tenantId);
37 return true;
38 }
39 
40 @Override
41 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
42 entityManager.unwrap(Session.class).disableFilter("tenantFilter");
43 }
44}
45 
46@Configuration
47@RequiredArgsConstructor
48public class WebConfig implements WebMvcConfigurer {
49 
50 private final TenantFilterInterceptor tenantFilterInterceptor;
51 
52 @Override
53 public void addInterceptors(InterceptorRegistry registry) {
54 registry.addInterceptor(tenantFilterInterceptor).addPathPatterns("/api/**");
55 }
56}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Hibernate filters let you inject a WHERE clause into every query without touching individual repositories.
  2. 2Enabling the filter per-request and disabling it afterward keeps tenant scoping tied to the request lifecycle and avoids leaking state across pooled connections.
  3. 3Centralizing tenant enforcement in an interceptor removes the risk of forgetting the tenant check in any single query.

Related explainers

java
@Component
@Converter
public class EncryptedStringConverter implements AttributeConverter<String, String> {
 

Transparent column encryption in Spring & JPA

encryption aes-gcm jpa-converter
Advanced 10 steps
python
import time
import uuid
 
from django.utils.deprecation import MiddlewareMixin

Attaching per-request context in Django

middleware request lifecycle multi-tenancy
Intermediate 7 steps
java
package com.acme.billing.config;
 
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;

Feature-flagged beans with Spring @ConditionalOnProperty

feature-flags conditional-beans strategy-pattern
Intermediate 5 steps
java
public static Map<String, String> parseCookieHeader(String header) {
    Map<String, String> cookies = new LinkedHashMap<>();
    if (header == null || header.isBlank()) {
        return cookies;

Parsing an HTTP Cookie header in Java

string-parsing http url-decoding
Intermediate 6 steps
java
public class TimedSocketReader {
 
    private static final int READ_TIMEOUT_MS = 5_000;
    private static final int CONNECT_TIMEOUT_MS = 3_000;

Reading a socket with connect and read timeouts

sockets timeouts io
Intermediate 8 steps
typescript
import { Injectable, Scope, Inject, NotFoundException } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
import { DataSource } from 'typeorm';

Per-tenant database connections in NestJS

multi-tenancy connection-pooling dependency-injection
Advanced 8 steps

Share this explainer

Here's the card — post it anywhere.

Multi-tenant row filtering with Hibernate in Spring — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code