aeron-io/aeron · error · ArchiveException
expected schemaId=
Error message
expected schemaId=
What it means
RecordingSignalAdapter.onFragment wraps the incoming SBE buffer as a MessageHeader and verifies the schemaId matches the archive protocol's MessageHeaderDecoder.SCHEMA_ID (1055). A mismatch means the fragment was not produced by the Aeron Archive control-protocol SBE schema, so decoding would be unsafe. The library throws ArchiveException to abort processing of the corrupted/foreign message.
Solutions
- Ensure only Aeron Archive control-protocol messages are published to the channel this adapter subscribes to
- Use matching aeron-archive and aeron-client jar versions on sender and receiver so the SBE schema id is identical
- Verify the fragment offset passed to the adapter comes directly from the Image/FragmentHandler (no manual offset arithmetic)
- Regenerate codecs from the same aeron-archive aeron-archive-codecs version used by the archive
Example fix
// before: subscribing to a shared app channel
Aeron.addSubscription("aeron:udp?endpoint=app.com", appStreamId, recordingSignalAdapter::onFragment);
// after: dedicated archive control channel/stream
Aeron.addSubscription("aeron:udp?endpoint=localhost:8010", archiveControlStreamId, recordingSignalAdapter::onFragment); Defensive patterns
Strategy: try-catch
Validate before calling
// verify the stream only carries archive control SBE messages and codecs match:
if (messageHeaderDecoder.schemaId() != MessageHeaderDecoder.SCHEMA_ID) { /* route to dead-letter/log */ } Type guard
boolean isArchiveSbeBuffer(DirectBuffer buffer, int offset) {
MessageHeaderDecoder h = new MessageHeaderDecoder();
h.wrap(buffer, offset);
return h.schemaId() == MessageHeaderDecoder.SCHEMA_ID;
} Try / catch
try { adapter.onFragment(buffer, offset, length, header); }
catch (ArchiveException e) { if (e.getMessage().startsWith("expected schemaId")) { log.error("foreign message on control stream", e); } } Prevention
- Keep aeron-archive codecs versions aligned across sender and receiver
- Publish only archive control messages on the control channel
- Never manually adjust fragment offsets before decoding
When it happens
Trigger: A fragment delivered to the control-response subscription whose first bytes are not an Archive SBE message header — e.g. the subscription is pointed at a publication sending raw/non-SBE payloads, a different SBE schema was used to encode, or a buffer offset bug causes the header to be read at the wrong position.
Common situations: Mixing Aeron versions where the archive schema id changed; a custom app publishing plain JSON/binary onto the channel that the RecordingSignalAdapter subscribes to; misconfigured replay/subscription URI receiving the wrong stream.
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
- expected schemaId=1055, actual=
- expected schemaId=1055, actual=
- expected schemaId=
- expected schemaId=
- expected schemaId=
AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12).
Data as JSON: /api/errors/1ca161c16e067c53.
Report an issue: GitHub.
Appendix: source
Thrown at aeron-archive/src/main/java/io/aeron/archive/client/RecordingSignalAdapter.java:112
public boolean isDone()
{
return isDone;
}
private ControlledFragmentHandler.Action onFragment(
final DirectBuffer buffer, final int offset, final int length, final Header header)
{
if (isDone)
{
return ABORT;
}
messageHeaderDecoder.wrap(buffer, offset);
final int schemaId = messageHeaderDecoder.schemaId();
if (schemaId != MessageHeaderDecoder.SCHEMA_ID)
{
throw new ArchiveException("expected schemaId=" + MessageHeaderDecoder.SCHEMA_ID + ", actual=" + schemaId);
}
switch (messageHeaderDecoder.templateId())
{
case ControlResponseDecoder.TEMPLATE_ID:
controlResponseDecoder.wrap(
buffer,
offset + MessageHeaderDecoder.ENCODED_LENGTH,
messageHeaderDecoder.blockLength(),
messageHeaderDecoder.version());
if (controlResponseDecoder.controlSessionId() == controlSessionId)
{
controlEventListener.onResponse(
controlSessionId,
controlResponseDecoder.correlationId(),
controlResponseDecoder.relevantId(),
controlResponseDecoder.code(),View on GitHub (pinned to 6d60124e15)