Ktor Tenant Context Adapter
Latest stable Based on Bluetape4k release 2.0.0
Problem
Section titled “Problem”JDK 25 adapter that binds a canonical TenantId to Ktor ApplicationCall.attributes with a one-call/one-tenant contract. The application owns plugins, authentication, header parsing, and HTTP status mapping. This manual connects that purpose to the current build, source entry points, tests, configuration resources, and lifecycle evidence instead of duplicating the README feature list.
When to use
Section titled “When to use”Use bluetape4k-ktor-tenant when an application must attach one authorized TenantId to each Ktor ApplicationCall and make it available across dispatcher hops that keep the same call. The application plugin or authentication pipeline remains responsible for parsing, authentication, authorization, and HTTP status mapping.
Coordinates
Section titled “Coordinates”dependencies { implementation(platform("io.github.bluetape4k:bluetape4k-dependencies:<version>")) implementation("io.github.bluetape4k:bluetape4k-ktor-tenant")}Gradle project path: :bluetape4k-ktor-tenant. Source directory: ktor/tenant.
Concepts
Section titled “Concepts”The first source-level concepts to inspect are KtorTenantContext, and TenantAlreadyBoundException. File names are navigation anchors; read each declaration and its tests before treating it as a public contract.
Quick start
Section titled “Quick start”Bind once near the start of the request pipeline after the raw input has been validated:
val tenant = authenticateAndResolveClinic(call.request).tenantIdKtorTenantContext.bindTenant(call, tenant)
service.find(KtorTenantContext.requireCurrent(call))Pass the same ApplicationCall to downstream code that needs the request-local binding.
API by task
Section titled “API by task”| Entry point | What to verify |
|---|---|
KtorTenantContext.bindTenant | Bind the first canonical tenant to the call without exposing a mutable clear or overwrite API. |
KtorTenantContext.currentOrNull | Read the optional request-local binding. |
KtorTenantContext.requireCurrent | Require the binding and fail with the common missing-context exception when absent. |
TenantAlreadyBoundException | Reject a second or concurrent binding without overwriting the first tenant. |
Patterns
Section titled “Patterns”Authenticate and resolve the canonical tenant before calling bindTenant, then bind exactly once per call. The ApplicationCall.attributes owner provides request-local lifecycle cleanup; do not add a global registry, mutable clear operation, nested rebind, or duplicate-binding recovery.
Integrations
Section titled “Integrations”The current build declares these integration edges:
api(project(":bluetape4k-tenant"))api(libs.ktor.server.core)Both dependencies are public API edges: consumers receive the common tenant contract and Ktor server core types with this adapter.
Configuration
Section titled “Configuration”The adapter has no configuration properties or installed Ktor plugin. Applications decide where authentication, canonicalization, binding, and error mapping occur in their own pipeline.
Failures
Section titled “Failures”currentOrNull returns null for an unbound call and requireCurrent throws the common MissingTenantContextException. A second or concurrent bindTenant call throws TenantAlreadyBoundException("Tenant context is already bound to this call") and preserves the first value.
Operations
Section titled “Operations”Do not include tenant values in logs, exceptions, MDC, or metric tags. Keep binding-failure telemetry bounded to carrier/stage labels and correlate it through existing trace or request identifiers.
Testing
Section titled “Testing”Run the module test task:
./gradlew :bluetape4k-ktor-tenant:test --no-configuration-cacheRepresentative test anchors:
Workshops
Section titled “Workshops”No dedicated workshop path is registered in the manual manifest. Use the module README and the representative tests above as runnable evidence.
Limitations
Section titled “Limitations”The module does not install an application plugin, authenticate or authorize tenants, parse headers, map exceptions to HTTP responses, or recover from duplicate binding. It provides no default tenant or process-global registry.