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

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