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

  1. Dump the batch with kafka-dump-log to inspect the raw version bytes.
  2. Restore the affected segment from a clean replica or KRaft snapshot.
  3. 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

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


AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03). Data as JSON: /data/errors/dd361cc3498086aa.json. Report an issue: GitHub.