Skip to content

Get Started

By the end of this guide you will have a Kotlin or Java application that appends events to Chronicle and reads a projected read model back. The entire example is a self-contained Gradle project.

build.gradle.kts
dependencies {
implementation("io.cratis:chronicle:0.1.0")
}
build.gradle
dependencies {
implementation 'io.cratis:chronicle:0.1.0'
}

ChronicleClient is the entry point. For local development use the development() factory, which connects to localhost:35000:

import io.cratis.chronicle.ChronicleClient
val client = ChronicleClient.development()
val store = client.getEventStore("MyApp")
import io.cratis.chronicle.ChronicleClient;
var client = new ChronicleClient(ChronicleOptions.Companion.development());
var store = client.getEventStore("MyApp");

For production, supply explicit options:

val client = ChronicleClient(
ChronicleOptions(host = "chronicle.internal", port = 35000)
)
var client = new ChronicleClient(
new ChronicleOptions("chronicle.internal", 35000)
);

Annotate a data class or Java class with @EventType.

The id is a stable string identifier for the event type across deployments.

import io.cratis.chronicle.events.EventType
@EventType(id = "EmployeeHired")
data class EmployeeHired(
val employeeId: String,
val firstName: String,
val lastName: String,
val department: String
)
import io.cratis.chronicle.events.EventType;
@EventType
public class EmployeeHired {
private String employeeId;
private String firstName;
private String lastName;
private String department;
public EmployeeHired() {}
public EmployeeHired(String employeeId, String firstName,
String lastName, String department) {
this.employeeId = employeeId;
this.firstName = firstName;
this.lastName = lastName;
this.department = department;
}
// Getters and setters
public String getEmployeeId() { return employeeId; }
public void setEmployeeId(String employeeId) {
this.employeeId = employeeId;
}
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() { return lastName; }
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getDepartment() { return department; }
public void setDepartment(String department) {
this.department = department;
}
}
val employeeId = "emp-001"
val result = store.eventLog.append(
eventSourceId = employeeId,
event = EmployeeHired(
employeeId = employeeId,
firstName = "Jane",
lastName = "Smith",
department = "Engineering"
)
)
if (result.isSuccess) {
println("Appended at sequence ${result.sequenceNumber.value}")
} else {
println("Failed: ${result.constraintViolations.map { it.message }}")
}
String employeeId = "emp-001";
var result = store.getEventLog().append(
employeeId,
new EmployeeHired(
employeeId,
"Jane",
"Smith",
"Engineering"
)
);
if (result.isSuccess()) {
System.out.println("Appended at sequence " +
result.getSequenceNumber().getValue());
} else {
String violations =
result.getConstraintViolations().stream()
.map(v -> v.getMessage())
.collect(Collectors.joining(", "));
System.out.println("Failed: " + violations);
}

A reactor observes events and performs side effects (see Reactors for the full model). Annotate the class with @Reactor and write one method per event type you want to handle.

import io.cratis.chronicle.observation.Reactor
@Reactor
class HrNotifications {
fun onEmployeeHired(event: EmployeeHired) {
println("Welcome ${event.firstName} ${event.lastName} " +
"to ${event.department}!")
}
}
// Register and start observing
store.reactors.register(HrNotifications())
import io.cratis.chronicle.observation.Reactor;
@Reactor
public class HrNotifications {
public void onEmployeeHired(EmployeeHired event) {
System.out.println("Welcome " + event.getFirstName() +
" " + event.getLastName() +
" to " + event.getDepartment() + "!");
}
}
// Register and start observing
store.getReactors().register(new HrNotifications());

A reducer folds a stream of events into a single mutable object (see Reducers for the full model). The @ReadModel marks the read model class, and @Reducer marks the reducer.

import io.cratis.chronicle.readModels.ReadModel
import io.cratis.chronicle.observation.Reducer
@ReadModel
data class EmployeeProfile(
val id: String = "",
val firstName: String = "",
val lastName: String = "",
val department: String = ""
)
@Reducer
class EmployeeProfileReducer {
fun on(event: EmployeeHired, state: EmployeeProfile): EmployeeProfile =
state.copy(
id = event.employeeId,
firstName = event.firstName,
lastName = event.lastName,
department = event.department
)
}
store.reducers.register(EmployeeProfileReducer())
import io.cratis.chronicle.readModels.ReadModel;
import io.cratis.chronicle.observation.Reducer;
@ReadModel
public class EmployeeProfile {
private String id = "";
private String firstName = "";
private String lastName = "";
private String department = "";
public EmployeeProfile() {}
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() { return lastName; }
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getDepartment() { return department; }
public void setDepartment(String department) {
this.department = department;
}
}
@Reducer
public class EmployeeProfileReducer {
public EmployeeProfile on(EmployeeHired event,
EmployeeProfile state) {
EmployeeProfile result =
state != null ? state : new EmployeeProfile();
result.setId(event.getEmployeeId());
result.setFirstName(event.getFirstName());
result.setLastName(event.getLastName());
result.setDepartment(event.getDepartment());
return result;
}
}
store.getReducers().register(new EmployeeProfileReducer());

After events have been projected, query the read model by its event source identifier:

val profile = store.readModels.getInstanceByKey(
EmployeeProfile::class,
employeeId
)
println(profile?.firstName) // Jane
EmployeeProfile profile = store.getReadModels()
.getInstanceByKey(EmployeeProfile.class, employeeId);
System.out.println(profile.getFirstName()); // Jane
  • Guides — deeper dives into reactors, projections, constraints, seeding, and compliance
  • Concepts — understand events, observers, and the read model pipeline
  • Reference — full annotation and API reference