apache/kafka · critical · InvalidRecordException
Invalid version found for control record: {}. May indicate d
Error message
Invalid version found for control record: {}. May indicate data corruption What it means
ControlRecordType.parseTypeId (line 95) reads a 2-byte version from the control-record key and rejects any value below ControlRecordTypeSchema.LOWEST_SUPPORTED_VERSION. Such a version maps to no known schema and is interpreted as corruption or a non-conformant writer rather than a future/unknown version (those above HIGHEST are tolerated by clamping).
Source
Thrown at clients/src/main/java/org/apache/kafka/common/record/internal/ControlRecordType.java:95
throw new IllegalArgumentException("Cannot serialize UNKNOWN control record type");
return buffer.duplicate();
}
public int controlRecordKeySize() {
return buffer.remaining();
}
public static short parseTypeId(ByteBuffer key) {
// We should duplicate the original buffer since it will be read again in some cases, for example,
// read by KafkaRaftClient and RaftClient.Listener
ByteBuffer buffer = key.duplicate();
if (buffer.remaining() < CONTROL_RECORD_KEY_SIZE)
throw new InvalidRecordException("Invalid value size found for control record key. " +
"Must have at least " + CONTROL_RECORD_KEY_SIZE + " bytes, but found only " + buffer.remaining());
short version = buffer.getShort();
if (version < ControlRecordTypeSchema.LOWEST_SUPPORTED_VERSION)
throw new InvalidRecordException("Invalid version found for control record: " + version +
". May indicate data corruption");
if (version > ControlRecordTypeSchema.HIGHEST_SUPPORTED_VERSION) {
log.debug("Received unknown control record key version {}. Parsing as version {}", version,
ControlRecordTypeSchema.HIGHEST_SUPPORTED_VERSION);
version = ControlRecordTypeSchema.HIGHEST_SUPPORTED_VERSION;
}
ControlRecordTypeSchema schema = new ControlRecordTypeSchema(new ByteBufferAccessor(buffer), version);
return schema.type();
}
public static ControlRecordType fromTypeId(short typeId) {
switch (typeId) {
case 0:
return ABORT;
case 1:
return COMMIT;
case 2:View on GitHub (pinned to c31c9215e1)
Solutions
- Dump the batch with kafka-dump-log to inspect the raw version bytes.
- Restore the affected segment from a clean replica or KRaft snapshot.
- Confirm no custom code is producing control record keys; only the broker/KRaft layer should write them.
Defensive patterns
Strategy: try-catch
Validate before calling
// Optional pre-check: read the version short yourself and reject below LOWEST_SUPPORTED_VERSION
import java.nio.ByteBuffer;
import org.apache.kafka.common.message.ControlRecordTypeSchema;
ByteBuffer dup = key.duplicate();
if (dup.remaining() >= 2) {
short version = dup.getShort(); // peek without consuming the original buffer
if (version < ControlRecordTypeSchema.LOWEST_SUPPORTED_VERSION) {
// reject as corrupt before calling parseTypeId
} else {
dup.rewind();
short typeId = ControlRecordType.parseTypeId(dup);
}
} Type guard
import org.apache.kafka.common.message.ControlRecordTypeSchema;
import java.nio.ByteBuffer;
static boolean hasValidControlRecordVersion(ByteBuffer key) {
if (key == null || key.remaining() < 2) return false;
short v = key.duplicate().getShort();
return v >= ControlRecordTypeSchema.LOWEST_SUPPORTED_VERSION;
}
// usage: if (hasValidControlRecordVersion(key)) { ControlRecordType.parseTypeId(key); } Try / catch
try {
short typeId = ControlRecordType.parseTypeId(key);
} catch (org.apache.kafka.common.InvalidRecordException e) {
// version below supported floor indicates data corruption;
// drop/repair the segment instead of retrying the same bytes
} Prevention
- An out-of-range version almost always means on-disk corruption or an incompatible writer; it is not transient.
- Forward-only versions above HIGHEST_SUPPORTED_VERSION are tolerated, but anything below LOWEST_SUPPORTED_VERSION is fatal to that key.
- Log the offending version and offset so you can locate the corrupt segment.
- Do not retry parseTypeId on the same bytes; quarantine or regenerate the data.
When it happens
Trigger: Deserialising a control record whose version short is negative or below the supported floor; typical when key bytes are bit-flipped or garbage; hit on any code path that calls ControlRecordType.parse/parseTypeId on a control batch.
Common situations: Disk/media corruption, memory bit-flips, mismatched or buggy custom serializers writing control-record keys, partially overwritten segment.
Related errors
- Invalid value size found for control record key. Must have a
- Expected %s control record type(%d), but found %s
- Invalid record size: expected {} bytes in record payload, bu
- 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/dd361cc3498086aa.json.
Report an issue: GitHub.