aeron-io/aeron · error · ArchiveException

expected schemaId=

Error message

expected schemaId=

What it means

AsyncAeronArchive.ControlResponseFragmentHandler.onFragment wraps the incoming buffer with the SBE MessageHeaderDecoder and throws ArchiveException when the decoded schemaId does not match the archive control-protocol schema id. The fragment on the control-response channel is not a valid archive control message, so it cannot be safely decoded.

Solutions

  1. Align the aeron-archive client and archive server versions so both use the same SBE schema id
  2. Ensure only the archive publishes on the control-response channel/stream you subscribed to
  3. Verify channel/stream configuration: the control response subscription should not carry unrelated traffic
  4. Clean build to avoid stale generated SBE codecs from a different schema version on the classpath

Example fix

// before
AeronArchive.connect(new AeronArchive.Context().controlResponseChannel("aeron:udp?endpoint=localhost:8010"));
// ...server on a different protocol version

// after
// pin matching versions
dependencies {
    implementation("io.aeron:aeron-archive:1.44.1") // same as archive server
}
Defensive patterns

Strategy: validation

Validate before calling

MessageHeaderDecoder h = new MessageHeaderDecoder().wrap(buffer, offset);
if (h.schemaId() != MessageHeaderDecoder.SCHEMA_ID) {
    throw new IllegalStateException("foreign message on control stream, schemaId=" + h.schemaId());
}

Type guard

boolean isArchiveControlSchema(DirectBuffer buf, int offset) {
    return new MessageHeaderDecoder().wrap(buf, offset).schemaId() == MessageHeaderDecoder.SCHEMA_ID;
}

Try / catch

try {
    asyncArchive.pollResponses();
} catch (ArchiveException e) {
    if (e.getMessage().startsWith("expected schemaId=")) {
        log.warn("non-archive traffic on control stream; check versions/publishers", e);
    }
}

Prevention

When it happens

Trigger: onFragment receives a buffer whose MessageHeaderDecoder.schemaId() differs from MessageHeaderDecoder.SCHEMA_ID — e.g. a foreign SBE-encoded message published onto the control-response stream, a mismatched archive client/server protocol version, or corrupted/interleaved traffic on the control channel.

Common situations: Pointing a client built against one aeron-archive version at an archive running a different SBE schema; another application publishing onto the same control-response stream/channel; stale jar mixing after an upgrade.

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

Appendix: source

Thrown at aeron-archive/src/main/java/io/aeron/archive/client/AsyncAeronArchive.java:295

        {
            state = State.CLOSED;

            CloseHelper.quietCloseAll(asyncConnect, aeronArchive);
        }
    }

    private ControlledFragmentHandler.Action onFragment(
        final DirectBuffer buffer,
        final int offset,
        final int length,
        final Header header)
    {
        messageHeaderDecoder.wrap(buffer, offset);

        final int schemaId = messageHeaderDecoder.schemaId();
        if (schemaId != MessageHeaderDecoder.SCHEMA_ID)
        {
            throw new ArchiveException("expected schemaId=" + MessageHeaderDecoder.SCHEMA_ID + ", actual=" + schemaId);
        }

        final int templateId = messageHeaderDecoder.templateId();
        switch (templateId)
        {
            case ControlResponseDecoder.TEMPLATE_ID:
                controlResponseDecoder.wrap(
                    buffer,
                    offset + MessageHeaderEncoder.ENCODED_LENGTH,
                    messageHeaderDecoder.blockLength(),
                    messageHeaderDecoder.version());

                if (controlResponseDecoder.controlSessionId() == controlSessionId)
                {
                    listener.onControlResponse(
                        controlResponseDecoder.correlationId(),
                        controlResponseDecoder.relevantId(),
                        controlResponseDecoder.code(),

View on GitHub (pinned to 6d60124e15)