Configuration reference
Published identities
Section titled “Published identities”| Component | Identity |
|---|---|
| Runtime | io.cratis:arc:<version> |
| KSP processor | io.cratis:arc-ksp:<version> |
| Spring Boot integration | io.cratis:arc-spring-boot-starter:<version> |
| Spring Data JPA | io.cratis:arc-spring-data-jpa:<version> |
| Spring Data MongoDB | io.cratis:arc-spring-data-mongodb:<version> |
| OpenAPI | io.cratis:arc-openapi-spring-boot-starter:<version> |
| Observability | io.cratis:arc-observability-spring-boot-starter:<version> |
| Chronicle integration | io.cratis:arc-chronicle-spring-boot-starter:<version> |
| Testing | io.cratis:arc-testing:<version> |
| Gradle plugin | Plugin 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.
Arc tenancy options
Section titled “Arc tenancy options”Source exposes immutable TenancyOptions for request adapters and tests. The defaults match Arc on .NET:
| Option | Default | Meaning |
|---|---|---|
headerName | x-cratis-tenant-id | Case-insensitive request header used by header resolution and subdomain fallback. |
queryParameterName | tenantId | Case-sensitive query parameter. |
claimType | tenant_id | Claim read from explicit context claims, then principal claims. |
baseDomain | Empty | Required by SubdomainTenantIdResolver; must contain at least two valid DNS labels and cannot be an address. |
fixedTenantId | development | Value 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.
Spring Boot properties
Section titled “Spring Boot properties”All properties use the cratis.arc prefix.
| Property | Default | Meaning |
|---|---|---|
endpoints.route-prefix | api | Prefix for conventional routes. |
endpoints.segments-to-skip-for-route | 0 | Leading package segments omitted from routes. Must be nonnegative. |
endpoints.include-command-name-in-route | true | Includes command names in conventional routes. |
endpoints.include-query-name-in-route | true | Includes query names in conventional routes. |
endpoints.enable-query-http-method | true | Adds RFC QUERY alongside GET for one-shot and observable snapshot routes. |
correlation-header | X-Correlation-ID | Java-friendly request and response header name used consistently by every route in the host. Must not be blank. |
correlation-enabled | true | Registers the host-wide correlation filter for /*. Disable it when the application owns correlation itself. |
tenant-header | x-cratis-tenant-id | Backward-compatible alias and default for tenancy.header-name. |
tenancy.resolvers | header | Ordered resolver precedence. Values are fixed, header, query, claim, subdomain, and development. |
tenancy.required | false | Rejects unresolved command, query, observable, and identity requests with 400 when enabled. |
tenancy.header-name | Unset | Case-insensitive header name; an unset value uses tenant-header. |
tenancy.query-parameter-name | tenantId | Exact case-sensitive query parameter name. |
tenancy.claim-type | tenant_id | Principal claim used for resolution and authenticated membership checks. |
tenancy.base-domain | Empty | Valid multi-label DNS base domain required by subdomain resolution. |
tenancy.fixed-tenant-id | development | Value returned by fixed and development resolution. |
tenancy.constrain-to-authenticated-claims | true | When authenticated claims of claim-type exist, the resolved tenant must be one of them. |
spring-data.jpa.command-transactions-enabled | false | Opts 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-enabled | false | Opts 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-timeout | 30s | Async servlet request and bounded first-result ceiling. |
coroutine-parallelism | 4 | Maximum actively executing Arc request coroutines; must be greater than zero. |
coroutine-queue-capacity | 256 | Maximum admitted Arc operations waiting for execution; zero permits no waiting. |
overload-retry-after-seconds | 5 | Retry-After value when general Arc request admission is exhausted. |
maximum-request-body-bytes | 1048576 | Maximum command or QUERY body, enforced for declared and streamed lengths. |
command-scope-completion-timeout | 5s | Independent cooperative timeout for each best-effort command execution-scope completion. |
platform-identity.enabled | false | Opts 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-policy | auto | always, never, or auto; auto secures HTTPS and every non-development profile. |
expose-exception-details | Profile-derived | When unset, enabled only for dev, development, or local profiles. |
observable-queries.wait-for-first-result-timeout | 30s | Maximum default wait for the first HTTP snapshot result. A request may shorten it. |
observable-queries.keep-alive-interval | 30s | Idle interval between hub Ping messages; zero disables heartbeats. |
observable-queries.connection-timeout | 30m | Maximum SSE connection lifetime; zero disables the transport timeout. |
observable-queries.maximum-connections | 1000 | Concurrent direct and multiplexed streaming connections. Exhaustion returns 503. |
observable-queries.maximum-subscriptions-per-connection | 100 | Active subscriptions on one hub connection. Exhaustion returns 429 for SSE POST. |
observable-queries.outbound-buffer-capacity | 64 | Pending outbound frames per connection before fail-closed cleanup. |
observable-queries.maximum-inbound-message-size | 65536 | Maximum WebSocket text message size in bytes. |
observable-queries.overload-retry-after-seconds | 5 | Retry-After value on 503/429 overload responses. |
observable-queries.web-socket-enabled | true | Registers 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:
| Property | Default | Meaning |
|---|---|---|
enabled | true | Decorates Arc execution contracts when a non-no-op ObservationRegistry bean exists. |
correlation-baggage-enabled | true | Places command and query correlation identifiers in OpenTelemetry baggage when its API is available. |
correlation-logging-enabled | true | Places 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.
Gradle plugin extension
Section titled “Gradle plugin extension”cratisArc fields:
| Field | Default | Meaning |
|---|---|---|
moduleName | Project name | Stable KSP artifact module name; cannot be blank. |
dependencyVersion | Plugin implementation version | Version for managed arc and arc-ksp dependencies. |
manageDependencies | true | Adds missing runtime and KSP dependencies. |
endpoints.routePrefix | api | Proxy route prefix. |
endpoints.segmentsToSkip | 0 | Package segments omitted from proxy routes. |
endpoints.includeCommandNames | true | Includes command names. |
endpoints.includeQueryNames | true | Includes query names. |
endpoints.enableQueryHttpMethod | true | Permits QUERY selection in proxies. |
proxies.enabled | true | Enables generation when an output directory exists. |
proxies.outputDirectory | Unset | Generated TypeScript directory. An unset value skips generation. |
proxies.removeStaleGeneratedFiles | true | Removes only stale Arc-marked files. |
proxies.segmentsToSkip | 0 | Package 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.
Binary validation annotation inputs
Section titled “Binary validation annotation inputs”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.
Dependency response-handler metadata
Section titled “Dependency response-handler metadata”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.
Manual KSP setup
Section titled “Manual KSP setup”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.
Jackson 3 migration
Section titled “Jackson 3 migration”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.