Migrate append routing
Since version 6.0.0 (released 2026-09-17), the Kotlin and Java client no longer chooses routing defaults on your behalf. This page is for applications upgrading from 5.x or earlier. Before upgrading, decide whether new events should use the kernel’s defaults or continue the routes your application already uses. Existing stored events are not moved or rewritten.
Before you deploy client 6.0.0 or later:
- Upgrade every kernel node. The 6.0.0 release notes require 18.4.1 or later.
- Pass all three route fields explicitly wherever you must keep the old route.
- Review stream-filtered reads, observers and concurrency scopes.
- Recompile every JVM consumer, including Java code.
Check the route your application needs
Section titled “Check the route your application needs”From client 6.0.0 on, omitted or empty routing resolves as follows. The client verifies its contract descriptor when it connects, so a kernel that cannot serve it refuses the connection before any append is sent.
| Route field | Previous single/rich-batch default | Kernel-owned default |
|---|---|---|
eventSourceType | Default | Default |
eventStreamType | Default | All |
eventStreamId | The event-source id | Default |
This is a breaking routing change, not just a serialization change.
Review stream-filtered observers, queries, and concurrency scopes before
switching. Earlier single-source appendMany calls also dropped supplied
routing options; they now honor them for every event in the batch.
A missing value (null) and an empty string both leave the decision to
the kernel. The client never replaces an explicit nonempty route, including
Default, All, or a stream id equal to the event-source id. An empty
string is not a way to select a legacy route.
Preserve the legacy route explicitly
Section titled “Preserve the legacy route explicitly”For single appends, set all three route fields to the values you intend to keep. These helpers accept your application’s annotated event object.
import io.cratis.chronicle.eventSequences.AppendOptionsimport io.cratis.chronicle.eventSequences.IEventSequence
suspend fun appendToLegacyRoute( sequence: IEventSequence, eventSourceId: String, event: Any) = sequence.append( eventSourceId, event, AppendOptions( eventSourceType = "Default", eventStreamType = "Default", eventStreamId = eventSourceId ))import io.cratis.chronicle.eventSequences.AppendResult;import io.cratis.chronicle.java.AppendOptionsBuilder;import io.cratis.chronicle.java.BlockingEventSequence;
class LegacyRouting { static AppendResult appendToLegacyRoute( BlockingEventSequence sequence, String eventSourceId, Object event) { var options = new AppendOptionsBuilder() .eventSourceType("Default") .eventStreamType("Default") .eventStreamId(eventSourceId) .build(); return sequence.append(eventSourceId, event, options); }}Pass the same options to appendMany(eventSourceId, events, options) for a
single-source batch. For a batch of EventForEventSourceId values, set
eventSourceType, eventStreamType, and eventStreamId on each value;
use that value’s own event-source id for the legacy stream id. Mixed
explicit and omitted routes remain mixed rather than inheriting a route
from another event.
If your application previously passed options to single-source batches, check stored event contexts instead of assuming those options took effect. To continue a stored route, supply its actual values explicitly.
Keep concurrency and compliance choices separate
Section titled “Keep concurrency and compliance choices separate”No concurrency defaults change: ordinary appends still use
ConcurrencyScope.none when omitted; an explicitly supplied none or
notSet is preserved. Rich batches still send exactly the scope-map entries
you supplied. A source omitted from that map stays omitted. Routing fields
are never copied into concurrency scopes automatically.
Subject behavior also stays unchanged: an omitted subject falls back to the event-source id, while an explicit subject is forwarded unchanged. Occurred time, tags, causation, identity, and correlation are retained when a single-source batch uses explicit routing.
Check how your reads narrow
Section titled “Check how your reads narrow”Reads narrow only by what you pass. A missing or empty filter means “do not narrow”, which is how the kernel reads an unspecified value - the client never substitutes a route of its own on a read, a tail lookup, or an observer tail.
The consequence is worth stating plainly, because it is the easiest way to lose
sight of new events after upgrading: an append that named no route is stored on
stream type All and stream id Default. A read narrowed to the route this
client used to choose - eventStreamType = "Default" - therefore returns none
of those events. It is a filter doing its job, not missing data.
import io.cratis.chronicle.eventSequences.AppendedEventimport io.cratis.chronicle.eventSequences.IEventSequenceimport kotlin.reflect.KClass
/** Every event for the source, whatever route the kernel resolved. */suspend fun readEveryRoute( sequence: IEventSequence, eventSourceId: String, eventTypes: List<KClass<*>>): List<AppendedEvent> = sequence.getForEventSourceIdAndEventTypes(eventSourceId, eventTypes)
/** Only the events the kernel routed by default. */suspend fun readKernelDefaultRoute( sequence: IEventSequence, eventSourceId: String, eventTypes: List<KClass<*>>): List<AppendedEvent> = sequence.getForEventSourceIdAndEventTypes( eventSourceId, eventTypes, eventStreamType = "All", eventStreamId = "Default")getForEventSourceIdAndEventTypes also takes an eventSourceType, which
narrows the read to one event source type. Omitting it, or passing Default,
returns every source type. It reached nothing before Chronicle 18.5.0 — the
request carried no such field, so a caller that passed it believed it had
filtered and had not.
Verify against the kernel
Section titled “Verify against the kernel”Read newly appended events back and check their route and metadata using
getForEventSourceIdAndEventTypes or getFromSequenceNumber. These reads
carry stored routing rather than guessing it from the append request.
EventContext now carries the compliance subject the kernel stored, which
changes its constructor and copy signatures. Recompile JVM consumers, including
Java callers, when upgrading.