Skip to content

Configuration

data class ChronicleOptions(
val connectionString: ChronicleConnectionString,
val programIdentifier: String = "Unknown",
val defaultSinkTypeId: String = System.getenv("CHRONICLE_SINK_TYPE") ?: WellKnownSinkTypes.MONGODB,
val autoDiscoverAndRegister: Boolean = true,
val artifacts: IClientArtifacts = ClientArtifacts.default,
val artifactActivator: IArtifactActivator = ArtifactActivator,
val openTelemetry: OpenTelemetry? = null
)
PropertyDefaultDescription
connectionString(required)Parsed address of the server
programIdentifier"Unknown"Name of the connecting program
defaultSinkTypeIdMongoDBSink for reducers and projections
autoDiscoverAndRegistertrueRegister artifacts on connect
artifactsClientArtifacts.defaultWhat the application is made of
artifactActivatorArtifactActivatorHow artifacts are created
openTelemetrynullWhere spans go — see Tracing
readModelNamingPolicyDefaultReadModelNamingPolicyNames each read model’s container — see Read model container names

programIdentifier is a human-readable label that shows up in diagnostics. defaultSinkTypeId defaults to WellKnownSinkTypes.MONGODB, and can be overridden per process with the CHRONICLE_SINK_TYPE environment variable — for example CHRONICLE_SINK_TYPE=SQL.

The last three control artifact discovery and registration. Two helpers cover the common adjustments — see Artifact Registration for the full picture:

ChronicleOptions.development().withoutAutoRegistration()
ChronicleOptions.development().withArtifactsFrom("com.acme.ordering")

readModelNamingPolicy is not a constructor parameter. Set it with withReadModelNamingPolicy, described below. copy does not carry it over, so set it again after copying.

There are two factories on the companion object:

ChronicleOptions.fromConnectionString(
"chronicle://chronicle.internal:35000?skipTlsValidation=false"
)
ChronicleOptions.development()

Both are @JvmStatic, so Java calls them as ordinary static methods:

ChronicleOptions.fromConnectionString(
"chronicle://chronicle.internal:35000?skipTlsValidation=false");
ChronicleOptions.development();

Every read model is stored in a container: a MongoDB collection or a SQL table. By default the client names it after the read model’s identifier — the @ReadModel(id = ...) value, or the class simple name. A Customer read model is stored in a container called Customer.

That is not always the name another layer expects. Arc’s MongoDB naming policy, for example, pluralizes, so it reads Customers while the client writes Customer. A ReadModelNamingPolicy decides the container name instead:

fun interface ReadModelNamingPolicy {
fun getReadModelName(readModelClass: Class<*>): String
}

Set one on the options. The policy names only the container. The read model identifier, and everything keyed on it — projections, reducers, observers, and the queries that read a model back — stays as it was.

import io.cratis.chronicle.readModels.ReadModelNamingPolicy
val options = ChronicleOptions.development()
.withReadModelNamingPolicy(
ReadModelNamingPolicy { "${it.simpleName}s" }
)
import io.cratis.chronicle.ChronicleOptions;
var options = ChronicleOptions.development()
.withReadModelNamingPolicy(
readModelClass -> readModelClass.getSimpleName() + "s");

A policy that already exists as an object, such as Arc’s, plugs in by delegating to it from the lambda. In a Spring Boot application, declare a ReadModelNamingPolicy bean and the starter uses it — see Spring Boot.

chronicle://<host>[:<port>][,<host>[:<port>]...][?<options>]
chronicle://<username>:<password>@<host>[:<port>][,...][?<options>]
chronicle+srv://<host>[:<port>][?<options>]

The port defaults to 35000 when omitted. IPv6 literals use bracket notation — chronicle://[::1]:35000. A chronicle+srv:// connection string accepts exactly one host, and DNS SRV records supply the real targets and ports.

OptionDefaultDescription
disableTlsfalseConnect over plaintext instead of TLS
skipTlsValidationtrueAccept any server certificate
apiKey(none)Rejected at client creation; see below
loadBalancerleast-connectionsPolicy across multiple addresses
srvNameServer(none)DNS server for chronicle+srv://

loadBalancer accepts least-connections, round-robin or random. Option names are matched without regard to case. An option name the client does not recognize is ignored without an error, so check the spelling of skipTlsValidation in particular: a misspelled one leaves certificate validation off. An unknown loadBalancer value throws IllegalArgumentException.

skipTlsValidation accepts self-signed certificates. Set it to false to require full certificate chain validation.

val options = ChronicleOptions.fromConnectionString(
"chronicle://my-client:my-secret@chronicle.internal:35000?skipTlsValidation=false"
)

ChronicleConnectionString.toString() renders a parsed connection string back to its chronicle:///chronicle+srv:// textual form. The result isn’t guaranteed to be byte-identical to whatever was originally parsed — for example a host without an explicit port is rendered with the resolved default port — but re-parsing it always yields an equal ChronicleConnectionString. This is useful for storing a connection string that was built up programmatically rather than typed by hand. The rendered string includes the password, so do not write it to logs; log its target (the first host and port) instead:

import io.cratis.chronicle.connection.ChronicleConnectionString
val original = ChronicleConnectionString.parse(
"chronicle://my-client:my-secret@chronicle.internal:35000?skipTlsValidation=false"
)
val rendered = original.toString()
val reparsed = ChronicleConnectionString.parse(rendered)
check(reparsed == original)
val client = ChronicleClient(ChronicleOptions.development())

Equivalent to a connection string of chronicle://chronicle-dev-client:chronicle-dev-secret@localhost:35000. It connects over TLS but skips certificate validation, so it works against the Kernel’s self-signed development certificate without further configuration.

The client connects over TLS by default. Certificate validation is skipped unless you set skipTlsValidation=false, which makes the client validate the certificate chain against the JVM’s default trust store. Set disableTls=true only for plaintext environments.

The client authenticates with a client id and secret: the user name and password in the connection string’s user info section. It exchanges them for an access token at the kernel’s /connect/token endpoint. Without user info it uses the development credentials, chronicle-dev-client and chronicle-dev-secret. Read the secret from the environment or a secret store rather than writing it into source code.

The connection string is parsed as text: everything after the first ? is read as options, and nothing is URL-decoded. A secret that contains ? cannot be written into it. Construct the connection string from its parts instead:

import io.cratis.chronicle.connection.ChronicleConnectionString
import io.cratis.chronicle.connection.ChronicleServerAddress
val options = ChronicleOptions(
ChronicleConnectionString(
addresses = listOf(ChronicleServerAddress("chronicle.internal", 35000)),
username = "my-client",
password = System.getenv("CHRONICLE_CLIENT_SECRET")
?: error("Set CHRONICLE_CLIENT_SECRET"),
skipTlsValidation = false
)
)

From Java, the constructor takes all nine properties in declaration order.

A kernel that is unreachable, or that rejects the client’s TLS settings, makes the client constructor throw. Wrong credentials are detected once the client tries to connect: the first append or awaitRegistration() throws ChronicleConnectionFailed. See Connection lifecycle for how each failure shows up.

See Namespaces for the full tenancy model. The default namespace is "Default". Override it when calling getEventStore:

val store = client.getEventStore("MyApp", namespace = "production")

From Java, BlockingChronicleClient has both forms, so you can name the namespace or leave it at the default:

import io.cratis.chronicle.ChronicleOptions;
import io.cratis.chronicle.java.BlockingChronicleClient;
import io.cratis.chronicle.java.BlockingEventStore;
var client = BlockingChronicleClient.connect(ChronicleOptions.development());
BlockingEventStore store = client.getEventStore("MyApp", "production");

ChronicleClient.getEventStore declares the namespace as a Kotlin default parameter without @JvmOverloads, so Java callers of that method must pass both arguments. See Java interop.