Quarkus Multitenancy

Quarkus Multitenancy is a Quarkiverse extension that provides a reusable tenant resolution and context propagation foundation for Quarkus applications.

It standardizes how a tenant identifier is resolved from incoming requests and exposes the resolved tenant through a consistent TenantContext.

The extension is designed around a small set of abstractions:

  • TenantResolver for resolving tenant identifiers from different sources.

  • TenantContext for exposing the resolved tenant during request processing.

  • HTTP tenant resolution strategies such as header, cookie, JWT claim, and path-based resolution.

  • ORM integration for tenant-aware persistence use cases.

Status

This extension is currently in preview while the API stabilizes.

Why this extension exists

Quarkus already provides powerful building blocks for multitenancy, such as OIDC multitenancy and Hibernate ORM multitenancy.

This extension focuses on a different layer: resolving the tenant identifier consistently and making it available to the rest of the application through a shared context.

In other words, Quarkus Multitenancy does not replace OIDC multitenancy or Hibernate ORM multitenancy. It complements them by providing a reusable tenant resolution contract.

Quarkus multitenancy vs OIDC multitenancy

When developers search for Quarkus multitenancy, they often mean OIDC multitenancy, where different OIDC tenants or providers are selected depending on the request.

Quarkus Multitenancy targets a different concern.

It focuses on resolving the tenant identifier from request data such as headers, cookies, JWT claims, or path segments, and exposing that value through TenantContext.

OIDC multitenancy focuses on authentication provider selection.

Quarkus Multitenancy focuses on tenant id resolution and propagation.

The two concepts can be used together, but they solve different problems.

Quarkus multitenancy and Hibernate ORM

Hibernate ORM multitenancy focuses on database, schema, or datasource isolation.

Quarkus Multitenancy provides the tenant resolution layer that can feed tenant-aware persistence routing.

For example, a request can resolve the tenant from the X-Tenant header and expose it through TenantContext. ORM integration can then use that resolved tenant to support tenant-aware persistence use cases.

Installation

For HTTP tenant resolution:

<dependency>
    <groupId>io.quarkiverse.multitenancy</groupId>
    <artifactId>quarkus-multitenancy-http</artifactId>
    <version>${quarkus-multitenancy.version}</version>
</dependency>

For ORM integration:

<dependency>
    <groupId>io.quarkiverse.multitenancy</groupId>
    <artifactId>quarkus-multitenancy-orm</artifactId>
    <version>${quarkus-multitenancy.version}</version>
</dependency>

Quickstart

Enable HTTP tenant resolution and resolve the tenant from the X-Tenant header:

quarkus.multi-tenant.http.enabled=true
quarkus.multi-tenant.http.strategy=header
quarkus.multi-tenant.http.header-name=X-Tenant
quarkus.multi-tenant.http.default-tenant=public

Example request:

curl -H "X-Tenant: tenant1" http://localhost:8080/api/users/tenant

The current tenant will be resolved as tenant1.

Basic usage

By default, the HTTP module runs the strategy chain header,jwt,cookie. Each strategy is tried in order, and the first resolver that returns a value wins.

Example showing the default chain in action (header resolves first because the request supplies X-Tenant):

quarkus.multi-tenant.http.enabled=true
quarkus.multi-tenant.http.header-name=X-Tenant
quarkus.multi-tenant.http.default-tenant=public

A request with:

X-Tenant: acme

will resolve the current tenant as acme.

Accessing the current tenant

Application code can inject TenantContext to access the resolved tenant during request processing.

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;

import io.quarkiverse.multitenancy.core.runtime.context.TenantContext;

@Path("/tenant")
public class TenantResource {

    @Inject
    TenantContext tenantContext;

    @GET
    public String currentTenant() {
        return tenantContext.getTenantId().orElse("public");
    }
}

Resolution strategies

Header

quarkus.multi-tenant.http.strategy=header
quarkus.multi-tenant.http.header-name=X-Tenant
quarkus.multi-tenant.http.strategy=cookie
quarkus.multi-tenant.http.cookie-name=tenant_cookie

Path

Path-based tenant resolution is useful for SaaS-style URLs where the tenant is part of the request path.

For example:

/t/acme/products

can resolve the tenant as acme.

Path-based resolution is opt-in. Enable it by adding path to the strategy list:

quarkus.multi-tenant.http.strategy=path,header,cookie
quarkus.multi-tenant.http.path-pattern=^/t/([^/]+)(?:/|$)
quarkus.multi-tenant.http.path-group=1

The path-pattern property defines the regular expression used to match the request path.

The path-group property defines which capturing group contains the tenant identifier.

With the default pattern:

^/t/([^/]+)(?:/|$)

the first capturing group extracts the tenant id from paths such as:

/t/acme
/t/acme/products
/t/customer-123/orders

This strategy does not rewrite the URL or perform request routing. It only extracts the tenant identifier and stores it in TenantContext.

JWT claim

The JWT resolver reads the tenant from a claim in a verified bearer token. SmallRye JWT performs the signature check before the resolver runs, so applications must configure a verification source before enabling the strategy.

quarkus.multi-tenant.http.strategy=jwt
quarkus.multi-tenant.http.jwt-claim-name=tenant

mp.jwt.verify.publickey.location=publicKey.pem
mp.jwt.verify.publickey.algorithm=RS256
mp.jwt.verify.issuer=https://my-issuer.example.com

Operator contract

  • Configure SmallRye JWT (any combination of mp.jwt.verify.publickey.location, mp.jwt.verify.publickey, or related properties) or Quarkus OIDC (quarkus.oidc.auth-server-url). The extension fails to start if jwt is in the strategy chain and neither source is configured. Set quarkus.multi-tenant.http.jwt.skip-startup-check=true to opt out — for example when wiring a custom JsonWebToken producer.

  • Pin the signature algorithm with mp.jwt.verify.publickey.algorithm. Never enable both symmetric and asymmetric verifiers at once.

  • The default-tenant fallback only applies when no strategy in the chain produces a result. A bearer token that fails verification or is missing the required claim rejects the request with HTTP 401; it does not silently fall back to the default tenant.

Resolution outcomes

Input Result

No Authorization: Bearer … header

JWT strategy returns NotApplicable; the dispatcher tries the next strategy.

Verified token whose jwt-claim-name claim is a non-blank string

Tenant is resolved to that string.

Verified token but the claim is missing, non-string, or blank

Request rejected with HTTP 401.

Bearer header present but the token cannot be verified (bad signature, wrong issuer, expired, …)

Request rejected with HTTP 401.

Configuration

The HTTP module currently exposes configuration under:

quarkus.multi-tenant.http.*

Main options include:

Property Default Description

quarkus.multi-tenant.http.enabled

true

Enables or disables HTTP tenant resolution.

quarkus.multi-tenant.http.strategy

header,jwt,cookie

Ordered list of built-in tenant resolution strategies.

quarkus.multi-tenant.http.header-name

X-Tenant

Header name used by the header resolver.

quarkus.multi-tenant.http.jwt-claim-name

tenant

JWT claim name used by the JWT resolver.

quarkus.multi-tenant.http.cookie-name

tenant_cookie

Cookie name used by the cookie resolver.

quarkus.multi-tenant.http.default-tenant

public

Tenant used when no resolver returns a value.

quarkus.multi-tenant.http.path-pattern

/t/([/]+)(?:/|$)

Regex used by the path resolver.

quarkus.multi-tenant.http.path-group

1

Capturing group used to extract the tenant from the path.

ORM configuration

The ORM module integrates the resolved tenant with Quarkus Hibernate ORM multitenancy.

For database-based multitenancy, enable Hibernate ORM multitenancy and define one datasource per tenant:

quarkus.hibernate-orm.multitenant=DATABASE

quarkus.datasource.__bootstrap.db-kind=postgresql
quarkus.datasource.__bootstrap.jdbc.url=jdbc:postgresql://localhost:5433/postgres
quarkus.datasource.__bootstrap.username=user1
quarkus.datasource.__bootstrap.password=pass1

quarkus.datasource.tenant1.db-kind=postgresql
quarkus.datasource.tenant1.jdbc.url=jdbc:postgresql://localhost:5433/tenant1
quarkus.datasource.tenant1.username=user1
quarkus.datasource.tenant1.password=pass1

quarkus.datasource.tenant2.db-kind=postgresql
quarkus.datasource.tenant2.jdbc.url=jdbc:postgresql://localhost:5434/tenant2
quarkus.datasource.tenant2.username=user2
quarkus.datasource.tenant2.password=pass2

The ORM adapter uses the tenant id stored in TenantContext to resolve the current Hibernate ORM tenant.

The special __bootstrap tenant is used as the default/bootstrap tenant before a request tenant is available.

FAQ

Is this the same as OIDC multitenancy?

No.

OIDC multitenancy focuses on selecting authentication provider configuration.

Quarkus Multitenancy focuses on resolving and propagating the tenant identifier used by the application.

Does this replace Hibernate ORM multitenancy?

No.

It complements Hibernate ORM multitenancy by providing consistent tenant resolution input for persistence routing.

Header-based resolution is useful for service-to-service or gateway-driven architectures.

JWT claim resolution is useful for authentication-centric flows where the tenant id is part of the token claims.

Cookie-based resolution is useful for browser applications.

Path-based resolution is useful for SaaS-style URLs such as /t/acme/products.

Demo

A demo application is available in the repository and can be used to test HTTP and ORM tenant resolution locally.

git clone https://github.com/quarkiverse/quarkus-multitenancy
cd quarkus-multitenancy/quarkus-multitenancy-demo
mvn quarkus:dev

Security note

The JWT tenant strategy verifies bearer tokens through SmallRye JWT or OIDC before reading the tenant claim, and rejects requests with HTTP 401 whenever a token is present but cannot be trusted. Operators are responsible for configuring a verification source and pinning the signature algorithm — see the operator contract under the JWT claim strategy.