java
43 lines · 8 steps
How a tenant-resolution filter works in Spring
A servlet filter reads a tenant header, validates it against the database, and binds it to thread-local context for the request.
Explained by
highlit
1@Component
2@Order(Ordered.HIGHEST_PRECEDENCE)
3public class TenantResolutionFilter extends OncePerRequestFilter {
4
5 private static final String TENANT_HEADER = "X-Tenant-ID";
6
7 private final TenantRepository tenantRepository;
8
9 public TenantResolutionFilter(TenantRepository tenantRepository) {
10 this.tenantRepository = tenantRepository;
11 }
12
13 @Override
14 protected void doFilterInternal(HttpServletRequest request,
15 HttpServletResponse response,
16 FilterChain filterChain) throws ServletException, IOException {
17 String tenantId = request.getHeader(TENANT_HEADER);
18
19 if (!StringUtils.hasText(tenantId)) {
20 response.sendError(HttpStatus.BAD_REQUEST.value(), "Missing " + TENANT_HEADER + " header");
21 return;
22 }
23
24 Tenant tenant = tenantRepository.findByExternalId(tenantId).orElse(null);
25 if (tenant == null || !tenant.isActive()) {
26 response.sendError(HttpStatus.FORBIDDEN.value(), "Unknown or inactive tenant");
27 return;
28 }
29
30 try {
31 TenantContext.setCurrentTenant(tenant);
32 response.setHeader(TENANT_HEADER, tenant.getExternalId());
33 filterChain.doFilter(request, response);
34 } finally {
35 TenantContext.clear();
36 }
37 }
38
39 @Override
40 protected boolean shouldNotFilter(HttpServletRequest request) {
41 return request.getRequestURI().startsWith("/actuator");
42 }
43}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Running a filter at highest precedence lets you establish request-scoped context before any other component executes.
- 2Thread-local context must always be cleared in a finally block to avoid leaking state across pooled request threads.
- 3Validating identity early and failing with the right HTTP status keeps invalid requests out of your business logic.
Related explainers
php
<?php namespace App\Experiments;
Weighted random selection in PHP
weighted-random
cumulative-sum
sampling
Intermediate
8 steps
java
@Repository public interface SubscriptionRepository extends JpaRepository<Subscription, Long> { @Modifying(clearAutomatically = true, flushAutomatically = true)
Bulk JPQL updates in a Spring Data repository
jpql
bulk-update
modifying-query
Intermediate
5 steps
go
package events import ( "net/http"
Two-pass JSON dispatch in Gin
polymorphic-json
request-binding
validation
Intermediate
8 steps
java
package com.example.uploads.config; import jakarta.servlet.MultipartConfigElement; import org.springframework.boot.web.servlet.MultipartConfigFactory;
Configuring upload size limits in Spring
file-upload
exception-handling
http-errors
Intermediate
6 steps
typescript
import { Injectable, PipeTransform, ArgumentMetadata,
A custom validation pipe in NestJS
validation
dto
recursion
Intermediate
10 steps
ruby
module CreditCard module_function def valid?(number)
Validating credit card numbers with Luhn
checksum
luhn-algorithm
validation
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/how-a-tenant-resolution-filter-works-in-spring-explained-java-53b5/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.