This blog post explains how to build a robust Spring Boot Multi-Tenancy Architecture, covering both Database-per-Tenant and Schema-per-Tenant models with dynamic routing based on request context β using Hibernate MultiTenancy support.
As SaaS applications grow, serving multiple customers (tenants) in an isolated yet scalable manner becomes critical. Thatβs where multi-tenancy comes in. Whether youβre managing separate databases per tenant or isolating tenants using schemas, Spring Boot provides the flexibility and extensibility needed to implement both.

π§© What is Multi-Tenancy?
Multi-tenancy allows a single application instance to serve multiple tenants (clients), each with isolated data and resources.
π Multi-Tenancy Strategies:
Strategy | Description | Isolation Level |
---|---|---|
Database-per-Tenant | Each tenant gets a separate database | High (best for compliance) |
Schema-per-Tenant | One DB, separate schemas per tenant | Medium |
Table-per-Tenant | Single schema, tenant ID column in all tables | Low (complex queries) |
In this post, we’ll focus on Database-per-Tenant β the most scalable and secure strategy.
π¦ Maven Dependencies
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency> </dependencies> |
ποΈ Project Structure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
com.kscodes.springboot.advanced β βββ config β βββ DataSourceBasedMultiTenantConnectionProviderImpl.java β βββ CurrentTenantIdentifierResolverImpl.java β βββ tenant β βββ TenantContext.java β βββ TenantFilter.java β βββ controller β βββ ProductController.java β βββ entity β βββ Product.java β βββ repository βββ ProductRepository.java |
π 1. Tenant Context Storage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package com.kscodes.springboot.advanced.tenant; public class TenantContext { private static final ThreadLocal<String> currentTenant = new ThreadLocal<>(); public static void setTenantId(String tenantId) { currentTenant.set(tenantId); } public static String getTenantId() { return currentTenant.get(); } public static void clear() { currentTenant.remove(); } } |
π§Ό 2. Tenant Filter (Extract from Header)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.kscodes.springboot.advanced.tenant; import jakarta.servlet.*; import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; public class TenantFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String tenantId = ((HttpServletRequest) request).getHeader("X-Tenant-ID"); TenantContext.setTenantId(tenantId); try { chain.doFilter(request, response); } finally { TenantContext.clear(); } } } |
βοΈ 3. Multi-Tenant Config (Database Routing)
CurrentTenantIdentifierResolverImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package com.kscodes.springboot.advanced.config; import com.kscodes.springboot.advanced.tenant.TenantContext; import org.hibernate.context.spi.CurrentTenantIdentifierResolver; public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver { @Override public String resolveCurrentTenantIdentifier() { String tenantId = TenantContext.getTenantId(); return (tenantId != null) ? tenantId : "default"; } @Override public boolean validateExistingCurrentSessions() { return true; } } |
DataSourceBasedMultiTenantConnectionProviderImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
package com.kscodes.springboot.advanced.config; import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; import java.util.Map; import java.util.HashMap; public class DataSourceBasedMultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider { private final Map<String, DataSource> dataSources = new HashMap<>(); public DataSourceBasedMultiTenantConnectionProviderImpl(Map<String, DataSource> sources) { this.dataSources.putAll(sources); } @Override public Connection getConnection(String tenantId) throws SQLException { return dataSources.get(tenantId).getConnection(); } @Override public Connection getConnection() throws SQLException { return getConnection("default"); } // other methods like isUnwrappableAs, supportsAggressiveRelease etc. } |
π§© 4. Enable Multi-Tenancy in Spring Config
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package com.kscodes.springboot.advanced.config; import org.springframework.context.annotation.*; import org.springframework.orm.jpa.*; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import javax.sql.DataSource; import java.util.*; @Configuration public class MultiTenantJpaConfig { @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSourceBasedMultiTenantConnectionProviderImpl connectionProvider, CurrentTenantIdentifierResolverImpl tenantResolver) { Map<String, Object> properties = new HashMap<>(); properties.put("hibernate.multiTenancy", "DATABASE"); properties.put("hibernate.multi_tenant_connection_provider", connectionProvider); properties.put("hibernate.tenant_identifier_resolver", tenantResolver); properties.put("hibernate.hbm2ddl.auto", "update"); LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setPackagesToScan("com.kscodes.springboot.advanced.entity"); em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); em.setJpaPropertyMap(properties); return em; } } |
πΎ 5. Repository + Entity
Product.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.kscodes.springboot.advanced.entity; import jakarta.persistence.*; @Entity public class Product { @Id @GeneratedValue private Long id; private String name; } |
ProductRepository.java
1 2 3 4 5 6 7 8 9 |
package com.kscodes.springboot.advanced.repository; import com.kscodes.springboot.advanced.entity.Product; import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository<Product, Long> {} |
π§ͺ 6. Controller for Testing
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package com.kscodes.springboot.advanced.controller; import com.kscodes.springboot.advanced.entity.Product; import com.kscodes.springboot.advanced.repository.ProductRepository; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/products") public class ProductController { private final ProductRepository repo; public ProductController(ProductRepository repo) { this.repo = repo; } @GetMapping public List<Product> getAll() { return repo.findAll(); } @PostMapping public Product create(@RequestBody Product product) { return repo.save(product); } } |
Test it with different X-Tenant-ID
headers to route to separate databases.
π‘οΈ Best Practices for Multi-Tenancy
Best Practice | Benefit |
---|---|
Use ThreadLocal with care | Prevent memory leaks |
Configure DataSource pooling per tenant | Avoid connection exhaustion |
Use tenant resolver at request start | For proper context in async flows |
Add fallback to default tenant | Avoid null pointers or crashes |
Validate tenant in header/token | Prevent spoofing |
π Strategy Comparison
Strategy | Performance | Isolation | Complexity |
---|---|---|---|
Database per tenant | Medium | High | Medium |
Schema per tenant | High | Medium | Medium |
Column per tenant | Highest | Low | High |
π Conclusion
Implementing Spring Boot Multi-Tenancy Architecture unlocks scalability, isolation, and efficiency in SaaS systems. Whether you’re managing 10 tenants or 1000, structuring your application to dynamically route database connections and resolve tenants cleanly is the cornerstone of a successful multi-tenant architecture.