Skip to content

Configuration reference

ComponentIdentity
Runtimeio.cratis:arc:<version>
KSP processorio.cratis:arc-ksp:<version>
Spring Boot integrationio.cratis:arc-spring-boot-starter:<version>
Spring Data JPAio.cratis:arc-spring-data-jpa:<version>
Spring Data MongoDBio.cratis:arc-spring-data-mongodb:<version>
OpenAPIio.cratis:arc-openapi-spring-boot-starter:<version>
Observabilityio.cratis:arc-observability-spring-boot-starter:<version>
Chronicle integrationio.cratis:arc-chronicle-spring-boot-starter:<version>
Testingio.cratis:arc-testing:<version>
Gradle pluginPlugin ID io.cratis.arc; implementation artifact io.cratis:arc-gradle-plugin:<version>

Local builds default to 0.0.0-SNAPSHOT; -Pversion=<version> overrides it. A local coordinate is not automatically available in a consumer build until it is published or supplied by a composite build.

Source exposes immutable TenancyOptions for request adapters and tests. The defaults match Arc on .NET:

OptionDefaultMeaning
headerNamex-cratis-tenant-idCase-insensitive request header used by header resolution and subdomain fallback.
queryParameterNametenantIdCase-sensitive query parameter.
claimTypetenant_idClaim read from explicit context claims, then principal claims.
baseDomainEmptyRequired by SubdomainTenantIdResolver; must contain at least two valid DNS labels and cannot be an address.
fixedTenantIddevelopmentValue returned by fixed and development resolvers.

Every resolver receives a TenantResolutionContext containing explicit headers, query parameters, host, claims, and principal. No resolver reads thread-local or ambient request state. CompositeTenantIdResolver tries resolvers in list order and returns the first nonblank result, so declaration order is the complete precedence rule.

SubdomainTenantIdResolver accepts exactly one DNS label before baseDomain and otherwise falls back to headerName. For example, acme.myapp.com resolves acme for myapp.com; myapp.com, one.two.myapp.com, unrelated hosts, invalid labels, and address literals use the fallback.

All properties use the cratis.arc prefix.

PropertyDefaultMeaning
endpoints.route-prefixapiPrefix for conventional routes.
endpoints.segments-to-skip-for-route0Leading package segments omitted from routes. Must be nonnegative.
endpoints.include-command-name-in-routetrueIncludes command names in conventional routes.
endpoints.include-query-name-in-routetrueIncludes query names in conventional routes.
endpoints.enable-query-http-methodtrueAdds RFC QUERY alongside GET for one-shot and observable snapshot routes.
correlation-headerX-Correlation-IDJava-friendly request and response header name used consistently by every route in the host. Must not be blank.
correlation-enabledtrueRegisters the host-wide correlation filter for /*. Disable it when the application owns correlation itself.
tenant-headerx-cratis-tenant-idBackward-compatible alias and default for tenancy.header-name.
tenancy.resolversheaderOrdered resolver precedence. Values are fixed, header, query, claim, subdomain, and development.
tenancy.requiredfalseRejects unresolved command, query, observable, and identity requests with 400 when enabled.
tenancy.header-nameUnsetCase-insensitive header name; an unset value uses tenant-header.
tenancy.query-parameter-nametenantIdExact case-sensitive query parameter name.
tenancy.claim-typetenant_idPrincipal claim used for resolution and authenticated membership checks.
tenancy.base-domainEmptyValid multi-label DNS base domain required by subdomain resolution.
tenancy.fixed-tenant-iddevelopmentValue returned by fixed and development resolution.
tenancy.constrain-to-authenticated-claimstrueWhen authenticated claims of claim-type exist, the resolved tenant must be one of them.
spring-data.jpa.command-transactions-enabledfalseOpts a verified fixed JPA persistence unit into an imperative, thread-bound command transaction scope. Dynamic tenant units are never auto-enrolled.
spring-data.mongodb.command-transactions-enabledfalseOpts a verified fixed MongoDB template with an identity-aligned manager into an imperative, thread-bound command transaction scope. Dynamic tenant stores are never auto-enrolled.
request-timeout30sAsync servlet request and bounded first-result ceiling.
coroutine-parallelism4Maximum actively executing Arc request coroutines; must be greater than zero.
coroutine-queue-capacity256Maximum admitted Arc operations waiting for execution; zero permits no waiting.
overload-retry-after-seconds5Retry-After value when general Arc request admission is exhausted.
maximum-request-body-bytes1048576Maximum command or QUERY body, enforced for declared and streamed lengths.
command-scope-completion-timeout5sIndependent cooperative timeout for each best-effort command execution-scope completion.
platform-identity.enabledfalseOpts into the optional Spring Security platform-header bridge. The default trust policy denies all ingress; supply an application ArcPlatformIdentityTrust and secure, header-rewriting ingress. With no application chain, all routes require authentication and CSRF stays enabled; see platform security.
identity-cookie-secure-policyautoalways, never, or auto; auto secures HTTPS and every non-development profile.
expose-exception-detailsProfile-derivedWhen unset, enabled only for dev, development, or local profiles.
observable-queries.wait-for-first-result-timeout30sMaximum default wait for the first HTTP snapshot result. A request may shorten it.
observable-queries.keep-alive-interval30sIdle interval between hub Ping messages; zero disables heartbeats.
observable-queries.connection-timeout30mMaximum SSE connection lifetime; zero disables the transport timeout.
observable-queries.maximum-connections1000Concurrent direct and multiplexed streaming connections. Exhaustion returns 503.
observable-queries.maximum-subscriptions-per-connection100Active subscriptions on one hub connection. Exhaustion returns 429 for SSE POST.
observable-queries.outbound-buffer-capacity64Pending outbound frames per connection before fail-closed cleanup.
observable-queries.maximum-inbound-message-size65536Maximum WebSocket text message size in bytes.
observable-queries.overload-retry-after-seconds5Retry-After value on 503/429 overload responses.
observable-queries.web-socket-enabledtrueRegisters WebSocket routes when Spring WebSocket is on the classpath.
observable-queries.allowed-origins(empty)Browser origins allowed to open an observable-query WebSocket. Empty means same-origin only.

The optional observability starter adds properties under cratis.arc.observability:

PropertyDefaultMeaning
enabledtrueDecorates Arc execution contracts when a non-no-op ObservationRegistry bean exists.
correlation-baggage-enabledtruePlaces command and query correlation identifiers in OpenTelemetry baggage when its API is available.
correlation-logging-enabledtruePlaces command and query correlation identifiers in SLF4J MDC when its API is available.

Correlation identifiers are observation context, baggage, and logging values, not metric or span tags. The starter never records tenant identifiers, user identifiers, command values, query arguments, claims, headers, or cookies.

cratisArc fields:

FieldDefaultMeaning
moduleNameProject nameStable KSP artifact module name; cannot be blank.
dependencyVersionPlugin implementation versionVersion for managed arc and arc-ksp dependencies.
manageDependenciestrueAdds missing runtime and KSP dependencies.
endpoints.routePrefixapiProxy route prefix.
endpoints.segmentsToSkip0Package segments omitted from proxy routes.
endpoints.includeCommandNamestrueIncludes command names.
endpoints.includeQueryNamestrueIncludes query names.
endpoints.enableQueryHttpMethodtruePermits QUERY selection in proxies.
proxies.enabledtrueEnables generation when an output directory exists.
proxies.outputDirectoryUnsetGenerated TypeScript directory. An unset value skips generation.
proxies.removeStaleGeneratedFilestrueRemoves only stale Arc-marked files.
proxies.segmentsToSkip0Package segments omitted from generated import layout.

Resolver configuration is validated at startup: the resolver list cannot be empty or contain duplicates, selected resolver options cannot be blank, fixed/development identifiers must be nonblank, and subdomain configuration must have a valid base domain. CompositeTenantIdResolver follows the listed order exactly. An application-defined TenantIdResolver or TenantAccessEvaluator bean replaces the default with Spring Boot’s normal @ConditionalOnMissingBean behavior.

The host captures case-insensitive headers, each query parameter’s first value, host, principal, and claims into an explicit TenantResolutionContext at request or subscription entry. Resolution is performed once and the resulting value is supplied as both tenantId and tenantNamespace; no thread-local state is used. Optional unresolved tenancy remains null for backward compatibility. Fixed or development resolution must be configured explicitly before either can supply a default. Access denial is generic and returns 403.

The plugin applies Kotlin/JVM 2.4.10 and KSP 2.3.11 (KSP2), targets JDK 17, and treats Kotlin and Java warnings as errors. Use Gradle 8.14.4, the repository’s wrapper baseline. See the compiler compatibility notes before retaining older consumer compiler or runtime pins. generateArcProxies depends on main classes, KSP, and resources, and build depends on it. Use the standard active JDK selection through JAVA_HOME and PATH; the repository requires no machine-specific absolute JDK path.

KSP diagnostics use stable ARCKSP codes. Configuration, command, query, proxy-shape, validation-metadata, enum-wire-value, and interoperability diagnostics are cataloged in CodeGeneration/KSP/DIAGNOSTICS.md; errors stop generation, while warnings identify risky but compilable conventions.

Published Kotlin APIs use checked .api baselines. The root apiCheck gate covers arc, KSP, the Gradle plugin implementation artifact, every published integration, and testing support; contract tests and runnable samples are intentionally unpublished and excluded. An API change therefore requires an explicit baseline update rather than silently changing compiler or plugin contracts. The temporal/UUID proxy slice changes generated TypeScript source types. Recursive shape metadata and source summaries were introduced in manifest format 6. Format 7 added optional typed command eventMetadata; current format 8 requires explicit property ignoreValidation metadata. Immutable public JVM shape descriptors, earlier constructor descriptors, and compatibility getters remain available. See the format-8 migration and Jakarta factory contract. Because the reader accepts exactly the current version, a dependency built against an earlier format must be rebuilt.

The runtime proxy harness has five wired unit tests plus 33 behavioral runtime tests. It runs four ignore-validation tests, four shared-fluent tests, and 15 general tests in separate UTC Node processes, then five calendar tests in a separate UTC process and the same five in a separate America/Los_Angeles process. Counts are parsed from each TAP summary, which must report its exact expected test/pass total and zero fail, cancelled, skipped, or todo results. Spring process-spawn errors fail cleanly instead of hanging startup.

Repository proxy verification has three distinct gates: deterministic generation plus strict TypeScript compilation with verbatimModuleSyntax, comparison with a repository-local semantically normalized .NET-derived expected fixture intended for source control, and :ContractTests:typeScriptRuntimeTest against the executable Kotlin Spring Boot sample. The differential compares sorted paths for all regular files and untouched JVM bytes, including headers, against prepared expected bytes. Only the expected side receives LF/trailing-whitespace handling, FixtureModel quote/indent preparation, CreateFixtures quote/import/layout/hook preparation with exact-site suppression, and five literal type-only import rewrites for verbatimModuleSyntax. An independent literal source table, cross-checked against fixture descriptors, validates 16 artifacts; queries use their declaring model. Uppercase SHA-256 headers are reconstructed from prepared expected bodies, never actual output, and three indexes stay headerless. Historical capture-time namespace/query-name casing and removed timestamps/hashes remain embedded in the fixture. SetCommandValues, ClearCommandValues, and query helper types such as PerformQuery, SetSorting, SetPage, SetPageSize, and ChangeSet become type-only imports. One additional expected-side correction at Commands/CreateFixtures.ts changes Command<ICreateFixtures, FixtureModel> to Command<ICreateFixtures, FixtureModel[]>. The captured command already calls super(FixtureModel, true), so its scalar generic contradicts enumerable runtime behavior. Only Models/Observe.ts also prepares the zero-space blank line immediately before the four-space-indented filter: string; member in exactly one known ObserveParameters block as four spaces; ObserveOne.ts is excluded. Missing/duplicate correction anchors and misplaced suppressions fail preparation. The .NET-derived FixtureModel.labelsByCategory capture already uses @field(Object) and Record<string, string>; no dictionary-shape rewrite is applied to it, although its file receives the documented formatting preparation. It proves that one string-key/string-value Record fixture, not non-string keys, nullable entries, typed model values, ValueMap, or broader dictionary parity. Contracts/Shape.ts is a class, not interface-emission proof. No normalization transforms JVM output. Capture SDK/tool versions remain unverified; this remains a normalized-fixture drift gate, not raw .NET-output equivalence, and overall parity remains Partial. The current .NET fixture contains no Guid, DateOnly, or TimeOnly, so focused generator and contract tests cover that mapping. See Generate TypeScript proxies for the complete rewrite inventory, source table, independent hash contract, and remaining capture-time reproducibility limitation.

The Arc plugin supplies KSP option arc.validationClasspath for each compilation: sorted absolute file URIs for the real compile-classpath directories/JARs, joined by |. The same file collection is a nonincremental classpath input, not a compile-ABI-only input. Private record field annotations can change without a public ABI change. KSP reads class-file field/accessor annotations without loading application classes, combining both sites instead of losing a field constraint when its accessor has another constraint of the same type.

Manual KSP wiring needs the matching option and input declaration; see the actual Kotlin sample and ContractTests build configurations linked from manual validation tooling. Do not pass an unrelated runtime classpath. Missing field evidence or conflicting/unreadable record definitions fail with ARCKSP0311; malformed input locations fail with ARCKSP0001. This option creates no new metadata resource and does not replace the format-8 manifest or the independent fluent declaration fingerprint. ArcFluentValidationNativeFunctionalTest proves private-field edits, unchanged-consumer invalidation, and fresh-output equivalence; the existing response-handler native fixture also proves ordinary unannotated binary records remain supported.

Arc KSP exports validated local @HandlesCommandResponseValues declarations to META-INF/cratis/arc-response-handlers/<moduleName>.json. This is a separate format-1 compiler/build transport, separate from the format-8 artifact manifest. A handler-only producer emits the declaration resource without manufacturing an artifact module, service entry, or artifact manifest. Imported declarations are never re-exported.

The producer document has exactly formatVersion, moduleName, and handlers:

{"formatVersion":1,"moduleName":"Audit","handlers":[{"handlerTypeName":"example.AuditHandler","handledTypeNames":["example.AuditEntry"]}]}

The aggregate index has exactly formatVersion: 1 and modules: [producer documents]; no dependencies produces {"formatVersion":1,"modules":[]}. Output is canonical UTF-8, with fixed property order, sorted modules, handlers, and handled names, and one terminal newline. Paths and timestamps are not serialized. Identical module documents deduplicate; the same module name with different declarations fails. Identical handler claims may appear in different modules, but conflicting claims fail. Different handlers may handle the same value. Duplicate handler entries or handled names within one document, duplicate JSON fields, unknown fields, malformed types, and unsupported versions fail closed.

Binary export initially supports public top-level handlers and public top-level handled types with ASCII canonical names. Other supported source-local declarations remain source-local. Consumers resolve indexed names through the current KSP resolver each round and verify their annotation and supported Kotlin/blocking/async handler SPI. Missing classes or disagreement fail with ARCKSP0102, naming the binary resource rather than inventing a source location. Invalid or unreadable arc.responseHandlerMetadata options fail with ARCKSP0001. The option accepts an absolute file: URI, including percent-encoded spaces; omitting it retains source-only discovery.

The Arc plugin registers extractMainArcResponseHandlerMetadata, extractTestArcResponseHandlerMetadata, and corresponding tasks for other JVM compilations. Each resolves its compilation’s dependency artifacts as JARs, not its own outputs. Main and test indexes are independent. Index wiring does not add a processor dependency to additional compilations: for generated test artifacts, also add io.cratis:arc-ksp:<version> to kspTest. ExtractArcResponseHandlerMetadata.dependencyArtifacts is @Classpath because resource changes matter, and the output is written only when its bytes change. ArcResponseHandlerMetadataArgumentProvider.metadataFile is path-insensitive. The index is also a nonincremental task input: a changed index requires complete KSP reclassification in that compilation and can rerun other processors there. Ordinary source edits and no-op builds retain the existing incremental behavior. Runtime registration and canHandle remain separate; missing or nonmatching runtime handlers still fail closed.

For manual Gradle/KSP builds, put io.cratis:arc-gradle-plugin:<version> on the buildscript classpath without applying io.cratis.arc. The following Groovy wiring is exercised by the native producer/consumer contract; retain your runtime, KSP, and repository declarations. Use the same published Arc version throughout (the local workspace version is 0.0.0-SNAPSHOT, available only after supplying its artifacts).

buildscript {
repositories { mavenCentral(); gradlePluginPortal() }
dependencies { classpath 'io.cratis:arc-gradle-plugin:0.0.0-SNAPSHOT' }
}
plugins { id 'org.jetbrains.kotlin.jvm'; id 'com.google.devtools.ksp' }
repositories { mavenCentral() }
dependencies {
implementation 'io.cratis:arc:0.0.0-SNAPSHOT'
ksp 'io.cratis:arc-ksp:0.0.0-SNAPSHOT'
}
kotlin { jvmToolchain(17) }
ksp { arg('arc.moduleName', 'Consumer') }
def extract = tasks.register('extractHandlers', io.cratis.arc.gradle.ExtractArcResponseHandlerMetadata) {
dependencyArtifacts.from(configurations.compileClasspath.incoming.artifactView {
attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE,
objects.named(LibraryElements, LibraryElements.JAR))
}.files)
outputFile.set(layout.buildDirectory.file('handler index/main.json'))
}
tasks.withType(com.google.devtools.ksp.gradle.KspAATask).configureEach {
if (name == 'kspKotlin') {
def provider = objects.newInstance(io.cratis.arc.gradle.ArcResponseHandlerMetadataArgumentProvider)
provider.metadataFile.set(extract.flatMap { it.outputFile })
commandLineArgumentProviders.add(provider)
inputs.file(provider.metadataFile).withPropertyName('arcResponseHandlerMetadata')
.withPathSensitivity(PathSensitivity.NONE)
}
}

Repeat this task-local wiring with the corresponding dependency configuration and a distinct output for each additional compilation. Do not use a global KSP argument provider for all source sets, include the compilation’s own classes, or mark the index input incremental.

Without the plugin, apply Kotlin/JVM and com.google.devtools.ksp, add io.cratis:arc to implementation, add io.cratis:arc-ksp to ksp, and set arc.moduleName in the KSP extension. Kotlin Spring applications also apply Kotlin’s Spring plugin. Add the Arc Spring starter and spring-boot-starter-webmvc for HTTP hosting.

Spring Boot 4 and Arc use the same Jackson 3 mapper. The starter contributes Arc’s module and builder customizer, so conventional MVC controllers and generated Arc endpoints share the same naming, inclusion, temporal, enum, concept, and polymorphism policy without an additional property. An application-supplied mapper remains authoritative; construct its Arc-configured replacement with ArcObjectMapper.configure(mapper) because Jackson 3 mappers are immutable.

Arc’s JSON API uses Jackson 3.1.x. Runtime types moved from com.fasterxml.jackson to tools.jackson, including ObjectMapper, JsonNode, modules, serializers, deserializers, and naming strategies. This is binary- and source-breaking for applications that called ArcObjectMapper, constructed ChangeSetComputer with a mapper, consumed introspection schemas, supplied an OpenAPI mapper, or referenced ArcJacksonModule directly. Replace those imports with their tools.jackson equivalents.

Jackson annotations are the deliberate exception: Jackson 3 still uses com.fasterxml.jackson.annotation, so application model annotations do not change package. Separate Java-time modules are no longer required because Jackson 3 embeds Java-time support.

ArcObjectMapper.configure(mapper) no longer mutates its argument. Jackson 3 mappers are immutable, so capture the configured copy:

val mapper = ArcObjectMapper.configure(JsonMapper.builder().build())

Java follows the same contract:

ObjectMapper mapper = ArcObjectMapper.configure(JsonMapper.builder().build());

The mapper must be a JSON JsonMapper; Jackson 3 does not permit a generic mapper to be repurposed for another format.