aeron-io/aeron · error · ArchiveException
expected schemaId=
Error message
expected schemaId=
What it means
ControlResponseAdapter.onFragment decodes the SBE MessageHeader of each fragment on the control-response stream and throws ArchiveException if the schemaId is not the expected archive control protocol schema id. This guards the adapter against decoding non-archive-control payloads as control responses.
Solutions
- Upgrade/downgrade aeron-archive so client and server share one schema version
- Dedicate the control-response channel/stream to archive control traffic only
- Rebuild with a clean classpath so generated codecs match MessageHeaderDecoder.SCHEMA_ID
- Log the offending schemaId and templateId before rethrowing to identify the foreign publisher
Example fix
// before
subscription.handler(this::onFragment); // any stream traffic lands here
// after
// validate before invoking the adapter
if (header.schemaId() == MessageHeaderDecoder.SCHEMA_ID) {
controlResponseAdapter.onFragment(buffer, offset, length, header);
} Defensive patterns
Strategy: validation
Validate before calling
MessageHeaderDecoder h = new MessageHeaderDecoder().wrap(buffer, offset);
if (h.schemaId() != MessageHeaderDecoder.SCHEMA_ID) { return; // skip foreign fragment
}
controlResponseAdapter.onFragment(buffer, offset, length, header); Type guard
boolean validSchema(UnsafeBuffer buf, int offset) {
messageHeaderDecoder.wrap(buf, offset);
return messageHeaderDecoder.schemaId() == MessageHeaderDecoder.SCHEMA_ID;
} Try / catch
try {
controlResponseAdapter.onFragment(buffer, offset, length, header);
} catch (ArchiveException e) {
if (e.getMessage().startsWith("expected schemaId=")) {
log.error("schema mismatch; client/server aeron-archive versions differ", e);
}
} Prevention
- Keep client and archive server on the same release train
- Dedicate control-response channels exclusively to archive traffic
- Verify codec regeneration after any SBE schema change
- Add a startup version handshake/log line to expose version skew early
When it happens
Trigger: onFragment is handed a fragment whose header schemaId != MessageHeaderDecoder.SCHEMA_ID: a mismatched client/server SBE schema version, foreign messages on the control-response stream, or a manually constructed/malformed buffer fed to the adapter.
Common situations: Version skew between aeron-archive jars (client vs server), routing unrelated application traffic to the archive control-response stream, classpath contamination with codecs generated from an older 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/d3c77cfbfd265c56.
Report an issue: GitHub.
Appendix: source
Thrown at aeron-archive/src/main/java/io/aeron/archive/client/ControlResponseAdapter.java:124
decoder.initialTermId(),
decoder.segmentFileLength(),
decoder.termBufferLength(),
decoder.mtuLength(),
decoder.sessionId(),
decoder.streamId(),
decoder.strippedChannel(),
decoder.originalChannel(),
decoder.sourceIdentity());
}
void 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);
}
switch (messageHeaderDecoder.templateId())
{
case ControlResponseDecoder.TEMPLATE_ID:
handleControlResponse(controlResponseListener, buffer, offset);
break;
case RecordingDescriptorDecoder.TEMPLATE_ID:
handleRecordingDescriptor(controlResponseListener, buffer, offset);
break;
case RecordingSignalEventDecoder.TEMPLATE_ID:
handleRecordingSignal(recordingSignalConsumer, buffer, offset);
break;
}
}
View on GitHub (pinned to 6d60124e15)