apache/kafka · error · IllegalArgumentException
Expected %s control record type(%d), but found %s
Error message
Expected %s control record type(%d), but found %s
What it means
ControlRecordUtils.validateControlRecordType (line 95) throws IllegalArgumentException when a control record's parsed type does not match the type the caller expected. Each typed deserialise helper (deserializeLeaderChangeMessage, deserializeSnapshotHeaderRecord, deserializeSnapshotFooterRecord, deserializeKRaftVersionRecord, deserializeVotersRecord) calls it as a guard so it only decodes the schema it understands.
Source
Thrown at clients/src/main/java/org/apache/kafka/common/record/internal/ControlRecordUtils.java:95
public static KRaftVersionRecord deserializeKRaftVersionRecord(ByteBuffer data) {
return new KRaftVersionRecord(new ByteBufferAccessor(data.slice()), KRAFT_VERSION_CURRENT_VERSION);
}
public static VotersRecord deserializeVotersRecord(Record record) {
ControlRecordType recordType = ControlRecordType.parse(record.key());
validateControlRecordType(ControlRecordType.KRAFT_VOTERS, recordType);
return deserializeVotersRecord(record.value());
}
public static VotersRecord deserializeVotersRecord(ByteBuffer data) {
return new VotersRecord(new ByteBufferAccessor(data.slice()), KRAFT_VOTERS_CURRENT_VERSION);
}
private static void validateControlRecordType(ControlRecordType expected, ControlRecordType actual) {
if (actual != expected) {
throw new IllegalArgumentException(
String.format(
"Expected %s control record type(%d), but found %s",
expected,
expected.type(),
actual
)
);
}
}
}
View on GitHub (pinned to c31c9215e1)
Solutions
- Parse record.key() first and dispatch by the actual ControlRecordType before calling the typed deserialiser.
- Confirm the controller/broker feature/version aligns between writer and reader.
- Re-fetch the snapshot or metadata log from a healthy source if records are misordered.
Example fix
// before
LeaderChangeMessage msg = ControlRecordUtils.deserializeLeaderChangeMessage(record);
// after
ControlRecordType type = ControlRecordType.parse(record.key());
if (type != ControlRecordType.LEADER_CHANGE) {
throw new IllegalStateException("Unexpected control type " + type);
}
LeaderChangeMessage msg = ControlRecordUtils.deserializeLeaderChangeMessage(record); Defensive patterns
Strategy: validation
Validate before calling
// Parse the control record type yourself and compare to the expected type before deserializing
import org.apache.kafka.common.record.ControlRecordType;
import org.apache.kafka.common.record.ControlRecordUtils;
import org.apache.kafka.common.record.Record;
ControlRecordType actual = ControlRecordType.parse(record.key());
if (actual == ControlRecordType.LEADER_CHANGE) {
// safe: type matches the specific deserializer
var msg = ControlRecordUtils.deserializeLeaderChangeMessage(record);
} else {
// wrong control record type for this deserializer; route to the correct handler
} Type guard
import org.apache.kafka.common.record.ControlRecordType;
import org.apache.kafka.common.record.Record;
static boolean isControlRecordType(Record r, ControlRecordType expected) {
return ControlRecordType.parse(r.key()) == expected;
}
// usage: if (isControlRecordType(record, ControlRecordType.SNAPSHOT_HEADER)) {
// ControlRecordUtils.deserializeSnapshotHeaderRecord(record);
// } Try / catch
try {
var msg = ControlRecordUtils.deserializeSnapshotHeaderRecord(record);
} catch (IllegalArgumentException e) {
// record was not the expected control record type; dispatch to the matching deserializer
} Prevention
- Each ControlRecordUtils.deserializeXxx(record) validates that the key encodes the matching ControlRecordType; call the one matching the parsed type.
- When dispatching control records, switch on ControlRecordType.parse(record.key()) and call only the matching deserializer.
- Do not assume record type from context (batch attributes, topic name); always parse the key.
- UNKNOWN control records should be skipped, not fed to a typed deserializer.
When it happens
Trigger: Calling one of ControlRecordUtils.deserializeXxx(record) on a record whose key parses to a different ControlRecordType; e.g. feeding a COMMIT/ABORT record into deserializeLeaderChangeMessage; processing a control batch whose record ordering differs from the reader's assumption.
Common situations: KRaft snapshot or leader-change handling reading the wrong record slot; controller/follower version or feature mismatch; misrouted control records; tests using the wrong control record fixture.
Related errors
- Cannot serialize UNKNOWN control record type
- Invalid value size found for control record key. Must have a
- Invalid version found for control record: {}. May indicate d
- Unknown topology description status id: {id}
- Unknown acknowledge type id: {id}
AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03).
Data as JSON: /data/errors/eae8b04d8202e335.json.
Report an issue: GitHub.