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

  1. Ensure all cluster nodes run the exact same Aeron version so generated codecs share one schema id
  2. Start the cluster from a snapshot produced by the same binary version, or take a fresh snapshot after upgrading
  3. If a protocol upgrade is required, follow Aeron's cluster migration procedure (backup, replay to a common version, re-snapshot)
  4. 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

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


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)