aeron-io/aeron · critical · ClusterException

expected schemaId= , actual=

Error message

expected schemaId=<SCHEMA_ID>, actual=<schemaId>

What it means

In ConsensusModuleAgent's replay/fragment handling, after dispatching to message decoders the agent verifies the SBE header schema id matched MessageHeaderDecoder.SCHEMA_ID; otherwise it throws. This protects log replay from decoding messages written with a different cluster protocol schema.

Solutions

  1. Ensure all cluster nodes run the same Aeron version so log schema ids are identical.
  2. Verify nothing external is publishing to the cluster log channel.
  3. Rebuild the cluster from a snapshot/archives produced with the current schema version.
  4. Clean the classpath of duplicate/old aeron-cluster codec artifacts.

Example fix

// before: node upgraded alone, log schema differs
// after: stop all nodes, snapshot, upgrade every node, restart
Defensive patterns

Strategy: try-catch

Try / catch

try { cluster.start(); } catch (ClusterException e) { if (e.getMessage().contains("expected schemaId")) { rebuildFromSnapshotWithCurrentSchema(); } else { throw e; } }

Prevention

When it happens

Trigger: During log or snapshot replay, a fragment's messageHeaderDecoder.schemaId() is not MessageHeaderDecoder.SCHEMA_ID, so none of the decoder branches handle it and the fallback throw is reached.

Common situations: Replaying a log produced by a different Aeron version with a changed cluster SBE schema; mixed-version cluster membership; corrupt log buffer contents from an external writer on the log channel.

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/d573e38c1e885d26. Report an issue: GitHub.

Appendix: source

Thrown at aeron-cluster/src/main/java/io/aeron/cluster/ConsensusModuleAgent.java:1536

        final Header header)
    {
        if (null != consensusModuleExtension)
        {
            final int remainingMessageOffset = offset + MessageHeaderDecoder.ENCODED_LENGTH;
            final int remainingMessageLength = length - MessageHeaderDecoder.ENCODED_LENGTH;

            return consensusModuleExtension.onLogExtensionMessage(
                actingBlockLength,
                templateId,
                schemaId,
                actingVersion,
                buffer,
                remainingMessageOffset,
                remainingMessageLength,
                header);
        }

        throw new ClusterException("expected schemaId=" + MessageHeaderDecoder.SCHEMA_ID + ", actual=" + schemaId);
    }

    void onReplayTimerEvent(final long correlationId)
    {
        if (!timerService.cancelTimerByCorrelationId(correlationId))
        {
            expiredTimerCountByCorrelationIdMap.getAndIncrement(correlationId);
        }
    }

    void onReplaySessionOpen(
        final long logPosition,
        final long correlationId,
        final long clusterSessionId,
        final long timestamp,
        final int responseStreamId,
        final String responseChannel)
    {

View on GitHub (pinned to 6d60124e15)