Skip to content

Releases: tenantlayer-io/tenantlayer

0.1.0 — first release

Choose a tag to compare

@suchait007 suchait007 released this 05 Sep 03:33

The tenant isolation layer for Spring Boot + Postgres.

Your query has no WHERE tenant_id. It returns only your tenant's rows anyway.

<dependency>
  <groupId>io.tenantlayer</groupId>
  <artifactId>tenantlayer-spring-boot-starter</artifactId>
  <version>0.1.0</version>
</dependency>

Getting started ·
Row-level security ·
All docs ·
API

Read Row-level security before deploying.
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 suite written
against a superuser connection passes whether or not your policies work.


First release. Thirty of the thirty-four features on the v0.1 roadmap.

Verified on Java 17 and 21, Hibernate 6.6 and 7.0, Spring Boot 3.5, Postgres 16. Every
isolation claim is mutation-tested — the implementation was broken deliberately and the
test confirmed to fail.

Isolation

  • Leak-proof Postgres RLS wiring: the tenant is set on every connection checkout rather
    than reset on return, so a missed reset cannot ride a tenant back into the pool
  • No tenant bound resolves to no rows, never all rows
  • One-shot RLS policy generation, emitting FORCE ROW LEVEL SECURITY, a nullif guard and
    the index the predicate needs
  • Tenant-scoped entity scanning, with include/exclude overrides
  • Hibernate @TenantId discriminator strategy

Resolution and authorisation

  • Header, subdomain, path-segment and JWT-claim resolvers, plus a pluggable
    TenantResolver SPI
  • Ordered resolver chain where order is precedence
  • Strict mode: an unresolvable tenant is rejected, never silently defaulted
  • Membership verification — the resolved tenant is checked against the authenticated
    principal, so a claimed tenant is not a granted one
  • Servlet filter ordering adapts automatically when resolution needs an authenticated
    principal

Propagation

  • @Async and Spring task executors
  • Virtual threads, including the SimpleAsyncTaskExecutor that Boot substitutes when
    spring.threads.virtual.enabled=true
  • CompletableFuture and arbitrary executors via TenantExecutors
  • @Scheduled jobs via TenantTasks.forEachTenant, which attempts every tenant and names
    the ones that failed
  • Outbound RestTemplate, RestClient, WebClient and Feign
  • Kafka produce and consume, including batch listeners, retries and recoverers

Registry, observability, testing

  • Table-backed tenant registry carrying status, region, group, datasource reference and
    metadata
  • MDC log enrichment
  • @WithTenant, IsolationAssertions.assertTenantCannotSee, and TenantPostgres
    Testcontainers fixtures that hand out a least-privileged connection rather than a
    superuser

Not included

  • ScopedValue context backing. The storage SPI ships with the ThreadLocal
    implementation; ScopedValue is a preview API until JDK 25 and shipping it would force
    --enable-preview on every consumer.
  • Schema-per-tenant and database-per-tenant routing (v0.2 / v0.3)
  • Reactor context propagation and Spring Batch (v0.2)

Known limitations

  • Code that unwraps a pooled connection to a raw PgConnection is outside enforcement from
    that point on
  • A superuser, or a table owner without FORCE ROW LEVEL SECURITY, bypasses row-level
    security by design — connect as a least-privileged role

Verifying this release

Every artifact is signed. To check it yourself:

gpg --keyserver keyserver.ubuntu.com --recv-keys 6464619381B491C9
gpg --verify tenantlayer-spring-boot-starter-0.1.0.jar.asc \
             tenantlayer-spring-boot-starter-0.1.0.jar

Expected: Good signature from "Suchait Gaurav <suchaitgaurav@gmail.com>",
key fingerprint 787D91F1 EAEB FAC8 265A 3AFA 6464 6193 81B4 91C9.

On the version number

0.1.0, not 1.0.0, and deliberately. Schema-per-tenant and database-per-tenant routing
are still to come, along with a configuration-driven strategy switch — and those will
change the shape of what you compile against. 0.x is the range that permits that
honestly. The API stabilises at 1.0, once all three isolation strategies exist and have
stress-tested the abstraction.

Found an isolation failure? Report it privately.