{"id":"dd361cc3498086aa","repo":"apache/kafka","slug":"invalid-version-found-for-control-record-may","errorCode":null,"errorMessage":"Invalid version found for control record: {}. May indicate data corruption","messagePattern":"Invalid version found for control record: (.+?)\\. May indicate data corruption","errorType":"exception","errorClass":"InvalidRecordException","httpStatus":null,"severity":"critical","filePath":"clients/src/main/java/org/apache/kafka/common/record/internal/ControlRecordType.java","lineNumber":95,"sourceCode":"            throw new IllegalArgumentException(\"Cannot serialize UNKNOWN control record type\");\n        return buffer.duplicate();\n    }\n\n    public int controlRecordKeySize() {\n        return buffer.remaining();\n    }\n\n    public static short parseTypeId(ByteBuffer key) {\n        // We should duplicate the original buffer since it will be read again in some cases, for example,\n        // read by KafkaRaftClient and RaftClient.Listener\n        ByteBuffer buffer = key.duplicate();\n        if (buffer.remaining() < CONTROL_RECORD_KEY_SIZE)\n            throw new InvalidRecordException(\"Invalid value size found for control record key. \" +\n                    \"Must have at least \" + CONTROL_RECORD_KEY_SIZE + \" bytes, but found only \" + buffer.remaining());\n\n        short version = buffer.getShort();\n        if (version < ControlRecordTypeSchema.LOWEST_SUPPORTED_VERSION)\n            throw new InvalidRecordException(\"Invalid version found for control record: \" + version +\n                    \". May indicate data corruption\");\n\n        if (version > ControlRecordTypeSchema.HIGHEST_SUPPORTED_VERSION) {\n            log.debug(\"Received unknown control record key version {}. Parsing as version {}\", version,\n                    ControlRecordTypeSchema.HIGHEST_SUPPORTED_VERSION);\n            version = ControlRecordTypeSchema.HIGHEST_SUPPORTED_VERSION;\n        }\n        ControlRecordTypeSchema schema = new ControlRecordTypeSchema(new ByteBufferAccessor(buffer), version);\n        return schema.type();\n    }\n\n    public static ControlRecordType fromTypeId(short typeId) {\n        switch (typeId) {\n            case 0:\n                return ABORT;\n            case 1:\n                return COMMIT;\n            case 2:","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/common/record/internal/ControlRecordType.java#L77-L113","documentation":"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).","triggerScenarios":"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.","commonSituations":"Disk/media corruption, memory bit-flips, mismatched or buggy custom serializers writing control-record keys, partially overwritten segment.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Optional pre-check: read the version short yourself and reject below LOWEST_SUPPORTED_VERSION\nimport java.nio.ByteBuffer;\nimport org.apache.kafka.common.message.ControlRecordTypeSchema;\n\nByteBuffer dup = key.duplicate();\nif (dup.remaining() >= 2) {\n    short version = dup.getShort(); // peek without consuming the original buffer\n    if (version < ControlRecordTypeSchema.LOWEST_SUPPORTED_VERSION) {\n        // reject as corrupt before calling parseTypeId\n    } else {\n        dup.rewind();\n        short typeId = ControlRecordType.parseTypeId(dup);\n    }\n}","typeGuard":"import org.apache.kafka.common.message.ControlRecordTypeSchema;\nimport java.nio.ByteBuffer;\n\nstatic boolean hasValidControlRecordVersion(ByteBuffer key) {\n    if (key == null || key.remaining() < 2) return false;\n    short v = key.duplicate().getShort();\n    return v >= ControlRecordTypeSchema.LOWEST_SUPPORTED_VERSION;\n}\n\n// usage: if (hasValidControlRecordVersion(key)) { ControlRecordType.parseTypeId(key); }","tryCatchPattern":"try {\n    short typeId = ControlRecordType.parseTypeId(key);\n} catch (org.apache.kafka.common.InvalidRecordException e) {\n    // version below supported floor indicates data corruption;\n    // drop/repair the segment instead of retrying the same bytes\n}","preventionTips":["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."],"tags":["control-record","data-corruption","deserialization"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}