Event sourcing in Kotlin and Java
Chronicle is an open-source (MIT) event-sourcing database and processing runtime, and io.cratis:chronicle is its idiomatic JVM client — one artifact, usable from both Kotlin and Java. Events are data classes or records annotated with @EventType, read models are classes annotated with @ReadModel, and reducers, reactors, projections, and constraints follow the same pattern. Artifacts on your classpath are found and registered with the kernel when you connect, so there is no schema file or registry to keep in sync. Kotlin gets suspending calls throughout; Java gets blocking bridges for the same surface. A Spring Boot starter reduces setup to a dependency, and chronicle-testing provides an in-process test library.
With event sourcing, your application appends immutable facts, and every view of the world is derived from them rather than overwritten on top of them. Chronicle stores those events and enforces rules — uniqueness and other constraints are checked at append time, on the server. Nothing is updated in place, so every state your system has been in is reconstructible.
The event store is not JVM-only: Chronicle’s kernel exposes a language-agnostic gRPC/protobuf boundary, and the same store is reachable from the .NET, TypeScript, and Elixir clients as well. Storage is pluggable — MongoDB by default, with PostgreSQL, SQL Server, SQLite, and in-memory providers implemented in the kernel.
Install
Section titled “Install”dependencies { implementation("io.cratis:chronicle:<version>") // Or, in a Spring Boot application: implementation("io.cratis:chronicle-spring-boot-starter:<version>")}A taste
Section titled “A taste”An annotated event, the read model it produces, and the reducer that folds one into the other — from the client repository’s example:
@EventTypedata class EmployeeHired( val firstName: String = "", val lastName: String = "", val title: String = "")
@ReadModeldata class EmployeeState( val id: String = "", val firstName: String = "", val title: String = "")
@Reducerclass EmployeeStateReducer { fun employeeHired(event: EmployeeHired) = EmployeeState( firstName = event.firstName, title = event.title)}No registration code is needed. Append an event and the read model is there:
store.eventLog.append("employee-1", EmployeeHired("Ada", "Lovelace", "Engineer"))val ada = store.readModels.getInstanceByKey(EmployeeState::class, "employee-1")The same surface is available from Java, with events as records and blocking bridges in place of suspending calls.
Where to go next
Section titled “Where to go next”Looking for another language? See Chronicle in your language for the .NET, TypeScript, and Elixir clients.