The Cratis Kotlin Application template
The cratis-kotlin template is the Kotlin flavor of the full-stack starting point. One command gives you a running application with the whole stack wired: Arc for commands and queries over HTTP, Chronicle for the event log, MongoDB for read models, and a React frontend that talks to generated TypeScript proxies — all authored in Kotlin on Spring Boot.
Install the package and scaffold:
dotnet new install Cratis.Templates.Kotlindotnet new cratis-kotlin -n MyStoreMongoDB is the default database. Pass --Database to scaffold against PostgreSQL, MsSql, or SQLite instead — the docker-compose.yml then provisions the matching database for the Chronicle kernel, and the read-model sink switches to SQL:
dotnet new cratis-kotlin -n MyStore --Database PostgreSQLThe application code itself is identical for all databases — read models are persisted by the Chronicle kernel, so only the infrastructure the kernel runs against changes.
What you get
Section titled “What you get”MyStore/├── build.gradle.kts — Gradle build with the Arc plugin├── settings.gradle.kts — plugin and dependency repositories├── gradlew, gradle/wrapper/ — Gradle wrapper (9.1.0)├── docker-compose.yml — Chronicle dev container + Aspire dashboard├── package.json — frontend tooling (Vite, React, Cratis Components)├── .frontend/ — Vite and TypeScript configuration├── App.tsx, Home.tsx — frontend application shell├── SomeModule/ — the sample frontend slices└── src/main/kotlin/com/example/ ├── CratisAppApplication.kt — Spring Boot entry point ├── ChronicleConfiguration.kt — Chronicle options and domain artifact registration └── somemodule/ — the sample backend vertical slicesThe sample slices teach the conventions
Section titled “The sample slices teach the conventions”The Registration slice shows the command side — a @Command record with a @CommandKey, returning the event to append. No controller, no route registration:
@Command@AllowAnonymousdata class Register( @CommandKey val id: SomeId, val name: SomeName) { fun handle(): Registered = Registered(name)}SomeId is a strongly-typed identity — a ConceptAs<String> — rather than a raw String. The command key routes the command to its event source; the generated TypeScript command sends it as part of the payload.
The Listing slice shows the read side — a read model built by a @Reducer from the Registered event, with a query exposing it over HTTP:
@ArcReadModel@ChronicleReadModel@AllowAnonymousdata class Listing( val id: String = "", val name: SomeName = SomeName("")) { companion object { @JvmStatic @Path("/api/listings") suspend fun all( @FromServices eventStore: IEventStore ): List<Listing> = eventStore.readModels.getInstances(Listing::class) }}Post to the command route and the whole loop runs — command handled, event appended to Chronicle, reactor notified, read model updated:
curl -sS -X POST http://localhost:8080/api/register \ -H 'Content-Type: application/json' \ -d '{"id":"'$(uuidgen)'","name":"Cratis"}'
curl -sS -X QUERY http://localhost:8080/api/listings \ -H 'Content-Type: application/json' \ -d '{"arguments":{}}'Versions
Section titled “Versions”The template pins the versions it was verified against — Arc 7.2.0 (Gradle plugin and arc-chronicle-spring-boot-starter, which pins Chronicle 4.0.0), Kotlin 2.4.20, Spring Boot 4.1.1, JDK 17, and Gradle 9.1.0. Upgrade by bumping these in build.gradle.kts, keeping the plugin and starter versions in sync.
AI assistance
Section titled “AI assistance”The scaffolded project includes a .cratis/ai.json with the Cratis AI profiles, languages, and coding agent harnesses for this template — the Cratis application selection for the Kotlin clients. Make sure the Cratis CLI is installed, then run:
cratis ai updateSee the CLI AI documentation for the full command reference.