Everything multi-tenant SaaS needs from Spring Boot and Postgres
Isolation the database enforces. Context that survives every thread, job and message. A test that proves it, not a README that claims it.
@Test
@WithTenant("acme")
void oneTenantCannotSeeAnother() {
assertThat(orders.findAll()).isNotEmpty(); // acme sees its own
assertTenantCannotSee("globex"); // and nothing of globex's
}0.1.0 · Apache 2.0 · on Maven Central · Java 17+ · Spring Boot 3.3+
The bug this exists for
Your connection pool reuses sessions. Set the tenant on one, miss the reset, and the next request inherits it — reading another customer’s data with no error, no exception, and nothing in your logs.
Your tests will not catch this. Tests don’t contend for pooled connections.
It is one of about six mistakes every hand-rolled multi-tenancy implementation makes, and they are all silent. TenantLayer is those six mistakes, already made and already fixed.
“Why not just add WHERE tenant_id = ?”
Because you will forget once, in four years, in a report query written by someone who has since left — and nothing will tell you.
And because the ORM is not the only thing that talks to your database. A native query, a JdbcTemplate call, a bulk update, a psql session on the same credentials: none of them go through your filter. All of them go through a row-level security policy.
The predicate belongs where it cannot be forgotten.
The whole surface
Multi-tenancy is not one feature. Here is every concern it involves, and exactly where we are on each — including the rows that are still empty.
| Concern | Shipped in 0.1.0 | Planned |
|---|---|---|
| Tenant resolution |
| |
| Context & propagation |
| |
| Isolation strategies |
| |
| Authorisation |
| — |
| Schema & policies |
| |
| Tenant registry |
| |
| Migrations | — | |
| Caching | — | |
| Observability |
| |
| Testing |
| |
| Dashboard | — |
Every planned item is an open issue. 26 of them are unclaimed.
Proof, not claims
Every isolation claim is mutation-tested
After a test passes, we break the implementation and confirm it fails. It has caught something real every time.
101 tests, on real infrastructure
Java 17 and 21, Hibernate 6.6 and 7.0, against a real Postgres and a real Kafka broker. Not mocks.
A consumer builds against the published artifact
On every commit. It caught a startup crash the library's own tests structurally could not find.
The bugs are written up, not buried
Enabling virtual threads silently removed tenant isolation from every @Async method. That is on the blog, not in a changelog footnote.
Get started
Three lines of dependency, two of configuration, one policy per table.
<dependency>
<groupId>io.tenantlayer</groupId>
<artifactId>tenantlayer-spring-boot-starter</artifactId>
<version>0.1.0</version>
</dependency>Read this before you deploy
Isolation depends on your application connecting as a role that is neither a superuser nor the table owner. Both bypass row-level security by design, and a test suite written against a superuser connection passes whether or not your policies work — including after someone deletes them. Row-level security →
Where this is
0.1.0 is a first release. The hard part — isolation, propagation, authorisation, the testing kit — is shipped and tested. Migrations, caching and the alternative isolation strategies are not.
0.x means the API may break between minor versions; every break gets a changelog entry with a migration note. It stabilises at 1.0, after all three isolation strategies exist and have stress-tested it between them.
If that is too early for you, watch the repository and come back at 0.3.