---
title: Annotation reference
description: Exact Arc annotations available to Kotlin and Java application models.
---


## Artifact annotations

| Annotation | Target | Contract |
| --- | --- | --- |
| `@Command` | Class | Marks a model-bound command with an instance `handle` method. |
| `@ReadModel` | Class | Marks a model whose static or companion methods are queries. |
| `@ExportedType` | Class | Makes a type an artifact root when no command or query references it. See [Exported types](#exported-types). |
| `@CommandKey` | Property, field, value parameter, getter | Selects the command identity. Required for plain Chronicle event responses. |
| `@FromServices` | Value parameter | Resolves a query parameter from the host service container instead of the request. Command handler dependencies are service-resolved by their generated signature. |
| `@TreatWarningsAsErrors` | Class, function | Makes information and warning validation feedback blocking by default. |

Generated command, model, read-model, and interface sequence properties require nonnullable elements. KSP reports `ARCKSP0300` for nullable entries, including Java record `List<@Nullable T>` type uses; nullable outer `List<T>?`, `Collection<T>?`, and `Array<T>?` remain supported. This is an earlier source rejection of an already unsupported manifest shape, not a source-compatible change. See [sequence properties](/arc/backend/kotlin/guides/typescript-proxies/#declare-nonnullable-sequence-elements).

Public declared Kotlin body properties participate alongside constructor properties: body `@CommandKey`, field/getter constraints, `@Valid`, and summaries are retained. Multiple constructor/body keys fail with `ARCKSP0106`. Member `@JsonIgnore` excludes state; computed or explicitly read-only Kotlin command input and unrepresentable body Jackson names/access fail with `ARCKSP0300`. Separate output-only computed models remain supported. See [command body state](/arc/backend/kotlin/guides/commands/#declare-body-state-explicitly) for ordering, Jackson mutability, and inheritance boundaries.

### Exported types

Code generation normally reaches model types through `@Command` and `@ReadModel`. A module that
contributes only identity detail types or shared data-transfer types has no such root, so it emits
no manifest and no generated client. `@ExportedType` makes the annotated type a root: it and every
type reachable from it are collected, and the compilation emits an artifact module and manifest
even with no commands and no queries.

Apply it to a public top-level concrete class, enum class, interface, or concept. Unsupported
targets — abstract classes, generic definitions, nonpublic types, and nested types — fail with
`ARCKSP0306`. Abstract bases are reached through their concrete derived types instead.

```kotlin
@ExportedType
public data class SampleIdentityDetails(public val source: String)
```

This is a deliberate divergence from Arc .NET, which discovers identity detail types by runtime
reflection over the classpath and exports whole assemblies with a `--library-mode` flag. KSP runs at
compile time, cannot see into function bodies, and cannot enumerate dependency types, so Arc.Kotlin
requires the marker rather than inferring roots and silently missing the ones it cannot observe.

### Kotlin visibility

KSP uses Kotlin visibility, not the presence of a `public` keyword. Implicit-public command/read-model classes, `handle`, `provide`, companion queries, constructor/body properties, and public interface overrides are supported. A public property with a private setter remains included; private, internal, and protected Kotlin state remains excluded. Generated framework declarations retain explicit `public` keywords.

Artifacts must still be top-level public classes. Nonpublic command/read-model classes fail with `ARCKSP0101`/`ARCKSP0200`; nonpublic `handle`/`provide`/query methods fail with `ARCKSP0102`/`ARCKSP0103`/`ARCKSP0201`. An actual query in a private or internal companion also fails with `ARCKSP0201`; unrelated private companion helpers do not become queries. Java package visibility is not public, and existing Java record/property handling is unchanged. This does not add a constructor-visibility requirement or broaden supported nested model shapes.

Previously omitted implicit-public properties now contribute fields, keys, validation, and reachable types to descriptors, manifests, and clients. Existing input-shape diagnostics also apply to them, including `ARCKSP0300` for computed command input. Implicit-public command-like methods and state now participate in the missing-`@Command` warning (`ARCKSP0100`), and implicit external handlers accepting an `@Command` are rejected with `ARCKSP0102`. Newly accepted `provide` methods retain the unused-value warning (`ARCKSP0107`); newly discovered duplicate keys fail with `ARCKSP0106`. Review regenerated clients and compiler diagnostics when upgrading; this is a source-acceptance correction with no ABI or manifest-format change.

## Validation annotations

[Shared fluent validators](/arc/backend/kotlin/guides/validation/) are discovered from public final direct `FluentModelValidator<T>` declarations, not a replacement annotation. They add validated explicit rules to the existing metadata graph and generated runtime registration. `ARCKSP0308` rejects unsupported constructor bodies, `ARCKSP0309` rejects unrepresentable rules, and `ARCKSP0310` rejects missing/conflicting compiler metadata. New shared `creditCard()` is rejected; the legacy annotation boundary below is unchanged.

| Annotation | Target | Contract |
| --- | --- | --- |
| `@IgnoreValidation` | Kotlin property/field/getter; Java instance field/bean getter/record component | Cuts one logical validation edge before access while keeping serialization and binding. See [member opt-out and Jakarta factory integration](/arc/backend/kotlin/reference/validation/#ignore-a-validation-member-edge), including `ARCKSP0311` boundaries. |
| `@Phone` | Field, property getter, value parameter, annotation class | Accepts empty values or strings containing only ASCII digits, JavaScript whitespace, parentheses, plus, and hyphen; use a presence constraint when empty is invalid. |
| `@Url` | Field, property getter, value parameter, annotation class | Accepts empty values or strings beginning with `http://` or `https://` and a nonempty value after the scheme; use a presence constraint when empty is invalid. |
| `@CreditCard` | Field, property getter, value parameter, annotation class | Accepts empty values or Luhn-valid ASCII card numbers containing optional spaces or hyphens; use a presence constraint when empty is invalid. |

`@Phone`, `@Url`, and `@CreditCard` are Jakarta constraints, work from Kotlin and Java, and remain in generated validation metadata. `@Phone` and `@Url` also emit matching TypeScript runtime rules. `@CreditCard` is server-only because the pinned `@cratis/arc` client runtime has no compatible credit-card rule; emitting the current .NET extractor shape would make the proxy uncompilable. Hibernate Validator `@URL` follows the same client mapping as `@Url` when present at compile time, while `@CreditCardNumber` has the same server-only boundary as `@CreditCard`. `null` is valid, so nullability remains the responsibility of `@NotNull`.

## Routing and transport annotations

| Annotation | Target | Values and behavior |
| --- | --- | --- |
| `@Path(value)` | Class, function | Overrides query path metadata. Explicit query paths are preserved verbatim. |
| `@QueryHttpMethod(value)` | Read-model class, function | Proxy preference: `AUTO` (default), `GET`, or `QUERY`. A class value defaults every query; a method value overrides it. |
| `@QueryTransport(value)` | Function | `REQUEST_RESPONSE` (default) or `OBSERVABLE`. KSP infers `OBSERVABLE` for Kotlin `Flow` and JDK `Flow.Publisher`; Spring hosts observable HTTP snapshots, direct SSE/WebSocket, and multiplexed hubs. |

## Command event metadata annotations

| Annotation | Target | Values and behavior |
| --- | --- | --- |
| `@CommandEventSourceType(value)` | Command class | Default event-source type for events returned by the command. |
| `@CommandEventStreamType(value)` | Command class | Default event-stream type for returned events. |
| `@CommandEventStreamId(value)` | Command class | Default event-stream identifier; when absent Chronicle uses the event-source identifier. |
| `@CommandEventSubject(value)` | Command class | Default compliance subject; when absent Chronicle uses the event-source identifier. |

Each annotation is optional. A present value must be nonblank and contain no control characters; KSP reports `ARCKSP0110` otherwise. Generated `CommandEventMetadata` retains absence as `null`, so Chronicle remains responsible for its own `Default` and event-source-ID fallbacks rather than Arc serializing those literals.

These are static command defaults. A command can implement `CommandEventStreamIdProvider` or `CommandEventSubjectProvider` when those values depend on its instance; generated code rejects a static annotation combined with the matching provider under `ARCKSP0110`, so one slot never has two sources. An explicit `EventForEventSourceId` value keeps its own stream/source/subject metadata, while a missing wrapper value inherits the composed command declaration. This deliberately preserves the JVM's existing explicit-event precedence.

## Authorization annotations

| Annotation | Target | Values and behavior |
| --- | --- | --- |
| `@AllowAnonymous` | Class, function | Allows unauthenticated access. |
| `@Authorize` | Class, function | Optional `policy`, `roles`, and `schemes` arrays. Requires an authenticated caller. |
| `@Roles(vararg value)` | Class, function | Repeatable declaration requiring at least one named role. |
| `@RolesContainer(value)` | Class, function | JVM container generated for repeated `@Roles`; application code normally does not use it directly. |

### Class and operation precedence

An operation — a command `handle` function or a read-model query function — that declares any `@Authorize` or `@Roles` replaces its class's declaration completely. Its policy, roles, and schemes are the only ones evaluated, and the class's are discarded. The class declaration applies only to operations that declare none. An operation can therefore only narrow access, never widen it: on a class requiring `admin`, a `@Roles("auditor")` operation admits auditors and rejects admins. Repeating `@Roles` on the same target still combines those roles, and a caller satisfies a role list by holding any one of its roles.

`@AllowAnonymous` cannot be combined with `@Authorize` or `@Roles`, on the same target or across a class and its operation. KSP reports `ARCKSP0108` and stops generation rather than resolving the combination. This is stricter than Arc .NET, which lets a method-level attribute override the class in that case; declare the artifact so that one level owns the decision.

## Serialization annotations

| Annotation | Target | Contract |
| --- | --- | --- |
| `@DerivedType(id)` | Class | Adds `_derivedTypeId` and registers a stable identifier for polymorphic Arc JSON. The identifier must be nonblank and unique for its base type. Code generation records the base-to-derivative mappings, and a host registers them so the identifier resolves when reading. See [Polymorphic reads](#polymorphic-reads). |
| `@Flags` | Class | Marks an enum as a bit field. Generated TypeScript gains an `all<Name>` constant combining every nonzero member. Nothing about JVM serialization changes. |
| `@ArcEnumValue(value)` | Field | Declares an enum member's integer wire value where KSP cannot prove it from a single integer-literal constructor argument. |

The annotation alone does not make a value readable again. Resolving an identifier back to a class is the job of `DerivedTypeRegistry`, and Arc never scans the classpath to fill it, so a base type's concrete types must be registered before it is first deserialized.

Serialization refuses a value whose base type is registered while the value's own type is not, and names both types. Such a value would otherwise be written with an identifier nothing can resolve and could never be read back. A type whose base type has no registrations at all is still written with its identifier, so a model that only travels to a client keeps working without a registry.

Arc writes an enum as an integer: the result of `value()` when the enum implements `ArcEnum`, and the ordinal otherwise. Reading accepts that integer or the member name matched case-insensitively, and rejects anything else.

### Polymorphic property declarations

Declare polymorphic properties as an interface or abstract base, not an ordinary concrete base class. KSP reports `ARCKSP0305` when a collected command, read-model, interface, or reachable DTO property declares a concrete class with a distinct, nonabstract `@DerivedType` descendant visible in the current compilation. The check also applies to nullable properties and the element type of supported collections and arrays. Annotating the concrete base itself with `@DerivedType` does not exempt it when it has descendants.

A concrete leaf remains legal, as does ordinary inheritance without annotated descendants. Visiting a superclass to collect inheritance metadata is not itself a prohibited property use. Existing map restrictions are unchanged.

This authoring restriction prevents a concrete base instance from writing JSON without `_derivedTypeId` that a registered polymorphic base requires on read. It is a property-use check, not a guarantee for every runtime registry configuration: changing a declaration to an interface or abstract base does not validate arbitrary multilevel registrations. Root read-model types are not rejected solely for being concrete polymorphic bases. KSP checks source descendants, including those generated in later processing rounds and source descendants of dependency bases; binary-only descendants or manually registered types not visible to KSP are outside this check. Verify those runtime configurations separately.

Source-visible derived leaves generated in later KSP rounds are included in the reachable metadata graph even when they are not commands or read models themselves. Arc refreshes existing interface-property derivative associations with that graph, allowing generated proxies to include the leaf model and its discriminator mapping. An unrelated annotated hierarchy is not included merely because it has `@DerivedType` annotations.

### Runtime derived-type dispatch

Populate the `DerivedTypeRegistry` before reading registered base types with `ArcObjectMapper`. On Arc's ordinary discriminator path, runtime dispatch resolves `_derivedTypeId` only within the declared base type's registrations; it does not infer self registrations, search other bases, or follow a transitive chain of identifiers. On that path, a non-null value must be an object with a textual, known identifier whose registered target is assignable to that base. Explicit JSON `null` remains `null`.

On this ordinary Arc path, manual runtime registrations support multilevel inheritance. For an abstract `Root`, an annotated concrete `Middle` extending `Root`, and an annotated `Leaf` extending `Middle`, register each intended pair explicitly: `Root` → `Middle`, `Root` → `Leaf`, and `Middle` → `Leaf`. Reading `Root` with the middle identifier creates exactly `Middle`, even though `Middle` is also a registered base. Reading either `Root` or `Middle` with the leaf identifier creates `Leaf`. Reading `Middle` with its own identifier still fails unless `Middle` → `Middle` was explicitly registered.

Arc consumes only the current object's discriminator before binding the selected target. Nested properties and collection elements independently validate their identifiers against their own declared bases. Compatible generic type bindings and property-specific Jackson bean configuration are retained during target binding. On this ordinary path, when a registered target declares no type parameters of its own and the requested base is parameterized, Arc also checks the target's fixed inherited bindings before looking up or invoking its deserializer. A definite contradiction, such as a target extending `Base<String>` requested as `Base<Payload>`, fails during reading with `JsonMappingException` naming the target, requested base, and conflicting binding path rather than returning a value that fails at typed payload access. The check recursively compares available generic arguments, collection elements, map keys and values, reference contents, and array components, projecting compatible subtypes onto the requested raw type before comparing their arguments.

This is read compatibility, not mutable generic invariance: a fixed `String` binding can be read as `String`, `Object`, or `CharSequence`, and a fixed `List<String>` as `Collection<Object>`. Raw or unconstrained requests are allowed; absent or unresolved actual metadata, including Jackson's indistinguishable `Object` bindings and erased wildcard constraints, cannot establish a definite contradiction and are not rejected by this check. It does not inspect payload values, validate custom deserializer output or application-defined type policies, or replace Jackson's existing specialization checks for targets with their own type parameters. JSON `null` bases remain `null`; a null payload does not excuse a definite binding contradiction. These are runtime mapper capabilities, not changes to KSP model authoring: the `ARCKSP0305` concrete-property restriction above and existing generic model restrictions remain in force.

The Arc wire format is unchanged: annotated objects carry one `_derivedTypeId` alongside their ordinary properties. Updating readers on the ordinary Arc path retain replacement-read semantics rather than mutating an existing value, and strict Jackson merge configuration rejects those updates.

Existing native Jackson type dispatch, configured through `@JsonTypeInfo` or default typing, remains Jackson-owned rather than constrained by the Arc registry. The Arc exact-identifier and assignability checks above apply to its ordinary discriminator path, not to native subtype selection. For example, a native name resolver can select a subtype absent from the Arc base's registrations without `_derivedTypeId`; adding `_derivedTypeId` can instead fail as an unknown property on that selected bean. Preserving this existing dispatch is not an Arc round-trip guarantee or a claim that all native combinations are supported, safe, or wire compatible. Separate hardening is tracked in issue #120.

### Flags combinations

`@Flags` does not give an enum the ability to carry a combination. A JVM enum constant is one named value, so `Read or Write` has a constant to deserialize into only when the enum declares one. A generated client can compose such a value — the emitted `all<Name>` constant exists to be combined with `|` — and the server answers with the ordinary safe `malformedRequest` envelope on both a command body and a query argument, without disclosing the enum type. Arc .NET draws the same boundary, because its enum converter gates reads on whether the integer is a defined member.

Two shapes carry a combination:

- Declare the combination as its own member, which gives it a value to write and a constant to read into.
- Declare a set of the enum, which writes an array of member wire values and accepts any combination:

  ```kotlin
  @Flags
  enum class Permission(private val wireValue: Int) : ArcEnum {
      None(0),
      Read(1),
      Write(2);

      override fun value(): Int = wireValue
  }

  data class Grant(val permissions: Set<Permission>)
  ```

  A `Grant` holding `Read` and `Write` writes `{"permissions":[1,2]}` and reads back into the same set.

### Polymorphic reads

`@DerivedType` on its own only affects writing. Resolving `_derivedTypeId` back to a class is the job of `DerivedTypeRegistry`, and Arc never scans the classpath to fill it. Code generation records every base-to-derivative mapping it saw on the generated `ArcArtifactModule` as real class references — the interface a value is declared as, and each base class above it — and the Spring Boot starter registers all of them into its `DerivedTypeRegistry` bean before Jackson reads anything.

Outside Spring, the same metadata populates a registry directly:

```kotlin
val registry = ConcurrentDerivedTypeRegistry()
ArcArtifactModuleRegistry.registerDerivedTypes(module, registry)
val mapper = ArcObjectMapper.create(registry)
```

A hierarchy that arrives from a dependency binary Arc's code generation never processed has no generated mapping. Register one with a `DerivedTypeRegistrar` bean; registrars run after the generated registrations, in Spring `@Order`:

```kotlin
@Bean
fun externalShapes() = DerivedTypeRegistrar { registry ->
    registry.register(Shape::class.java, ExternalCircle::class.java)
}
```

An application that declares its own `DerivedTypeRegistry` bean replaces Arc's entirely, generated registrations included.

Reading fails closed. An identifier the registry cannot resolve, and a value whose base type has registrations but that carries no `_derivedTypeId`, are both refused with a mapping error that names the base type; over HTTP that becomes the ordinary safe `malformedRequest` envelope. Arc .NET refuses an unresolvable identifier the same way, but returns `null` when the discriminator is absent — an intentional divergence, because a silently absent property is harder to diagnose than a rejected request.
