aeron-io/aeron · error · ClusterException
expected schemaId= , actual=
Error message
expected schemaId=${expected}, actual=${actual} What it means
ConsensusModuleSnapshotAdapter.onFragment validates the SBE schema id on every snapshot fragment before decoding. The snapshot stream was produced with a different message schema than the one this binary was compiled against, so decoding is aborted with a ClusterException rather than misinterpreting bytes.
Solutions
- Ensure all cluster nodes run the exact same Aeron version so generated codecs share one schema id
- Start the cluster from a snapshot produced by the same binary version, or take a fresh snapshot after upgrading
- If a protocol upgrade is required, follow Aeron's cluster migration procedure (backup, replay to a common version, re-snapshot)
- Regenerate SBE codecs only from the schema version that matches the recorded data
Example fix
// before: replaying a snapshot recorded by an older Aeron version after upgrading the node ClusterException: expected schemaId=101, actual=99 // after: re-snapshot with the matching version, or pin the cluster to one Aeron version across all members mvn dependency:get -Dartifact=io.aeron:aeron-all:<same-version-on-all-nodes>
Defensive patterns
Strategy: try-catch
Validate before calling
// Verify snapshot provenance before loading: same Aeron version and same cluster dir String aeronVersion = AeronVersion.VERSION; // compare with the version that wrote the snapshot recording (stored in appVersion via onLoadBeginSnapshot)
Type guard
boolean isCompatibleSchema(int schemaId) { return MessageHeaderDecoder.SCHEMA_ID == schemaId; } Try / catch
try {
snapshotPlayer.load(...);
} catch (ClusterException e) {
if (e.getMessage().startsWith("expected schemaId=")) {
throw new IllegalStateException("Snapshot schema mismatch — restore a snapshot from the same Aeron version", e);
}
throw e;
} Prevention
- Pin all cluster members to the same Aeron version (test in CI)
- Take a fresh snapshot immediately after upgrades before relying on restores
- Keep backups of both the snapshot and the binary version that produced it
When it happens
Trigger: Loading a cluster snapshot recording made by an older/newer Aeron version (or with a different generated SBE schema) than the ConsensusModule doing the replay; the header schemaId read from the buffer does not equal MessageHeaderDecoder.SCHEMA_ID.
Common situations: Rolling cluster upgrade/downgrade where nodes have mismatched Aeron versions; restoring a snapshot from an old archive after upgrading the cluster module; mixing snapshots taken with a custom-built SBE schema.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- snapshot ended unexpectedly
- expected schemaId= , actual=
- expected schemaId= , actual=
- expected schemaId= , actual=
- snapshot ended unexpectedly
AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12).
Data as JSON: /api/errors/a63f682828ef48f6.
Report an issue: GitHub.
Appendix: source
Thrown at aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleSnapshotAdapter.java:72
boolean isDone()
{
return isDone;
}
int poll()
{
return image.controlledPoll(fragmentAssembler, FRAGMENT_LIMIT);
}
@SuppressWarnings("MethodLength")
public Action onFragment(final DirectBuffer buffer, final int offset, final int length, final Header header)
{
messageHeaderDecoder.wrap(buffer, offset);
final int schemaId = messageHeaderDecoder.schemaId();
if (MessageHeaderDecoder.SCHEMA_ID != schemaId)
{
throw new ClusterException("expected schemaId=" + MessageHeaderDecoder.SCHEMA_ID + ", actual=" + schemaId);
}
switch (messageHeaderDecoder.templateId())
{
case SessionMessageHeaderDecoder.TEMPLATE_ID:
sessionMessageHeaderDecoder.wrap(
buffer,
offset + MessageHeaderDecoder.ENCODED_LENGTH,
messageHeaderDecoder.blockLength(),
messageHeaderDecoder.version());
listener.onLoadPendingMessage(sessionMessageHeaderDecoder.clusterSessionId(), buffer, offset, length);
break;
case SnapshotMarkerDecoder.TEMPLATE_ID:
snapshotMarkerDecoder.wrap(
buffer,
offset + MessageHeaderDecoder.ENCODED_LENGTH,View on GitHub (pinned to 6d60124e15)