{"id":"eae8b04d8202e335","repo":"apache/kafka","slug":"expected-s-control-record-type-d-but-found-s","errorCode":null,"errorMessage":"Expected %s control record type(%d), but found %s","messagePattern":"Expected (.+?) control record type\\((.+?)\\), but found (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/common/record/internal/ControlRecordUtils.java","lineNumber":95,"sourceCode":"\n    public static KRaftVersionRecord deserializeKRaftVersionRecord(ByteBuffer data) {\n        return new KRaftVersionRecord(new ByteBufferAccessor(data.slice()), KRAFT_VERSION_CURRENT_VERSION);\n    }\n\n    public static VotersRecord deserializeVotersRecord(Record record) {\n        ControlRecordType recordType = ControlRecordType.parse(record.key());\n        validateControlRecordType(ControlRecordType.KRAFT_VOTERS, recordType);\n\n        return deserializeVotersRecord(record.value());\n    }\n\n    public static VotersRecord deserializeVotersRecord(ByteBuffer data) {\n        return new VotersRecord(new ByteBufferAccessor(data.slice()), KRAFT_VOTERS_CURRENT_VERSION);\n    }\n\n    private static void validateControlRecordType(ControlRecordType expected, ControlRecordType actual) {\n        if (actual != expected) {\n            throw new IllegalArgumentException(\n                String.format(\n                    \"Expected %s control record type(%d), but found %s\",\n                    expected,\n                    expected.type(),\n                    actual\n                )\n            );\n        }\n    }\n}\n","sourceCodeStart":77,"sourceCodeEnd":106,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/common/record/internal/ControlRecordUtils.java#L77-L106","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nLeaderChangeMessage msg = ControlRecordUtils.deserializeLeaderChangeMessage(record);\n// after\nControlRecordType type = ControlRecordType.parse(record.key());\nif (type != ControlRecordType.LEADER_CHANGE) {\n    throw new IllegalStateException(\"Unexpected control type \" + type);\n}\nLeaderChangeMessage msg = ControlRecordUtils.deserializeLeaderChangeMessage(record);","handlingStrategy":"validation","validationCode":"// Parse the control record type yourself and compare to the expected type before deserializing\nimport org.apache.kafka.common.record.ControlRecordType;\nimport org.apache.kafka.common.record.ControlRecordUtils;\nimport org.apache.kafka.common.record.Record;\n\nControlRecordType actual = ControlRecordType.parse(record.key());\nif (actual == ControlRecordType.LEADER_CHANGE) {\n    // safe: type matches the specific deserializer\n    var msg = ControlRecordUtils.deserializeLeaderChangeMessage(record);\n} else {\n    // wrong control record type for this deserializer; route to the correct handler\n}","typeGuard":"import org.apache.kafka.common.record.ControlRecordType;\nimport org.apache.kafka.common.record.Record;\n\nstatic boolean isControlRecordType(Record r, ControlRecordType expected) {\n    return ControlRecordType.parse(r.key()) == expected;\n}\n\n// usage: if (isControlRecordType(record, ControlRecordType.SNAPSHOT_HEADER)) {\n//            ControlRecordUtils.deserializeSnapshotHeaderRecord(record);\n//        }","tryCatchPattern":"try {\n    var msg = ControlRecordUtils.deserializeSnapshotHeaderRecord(record);\n} catch (IllegalArgumentException e) {\n    // record was not the expected control record type; dispatch to the matching deserializer\n}","preventionTips":["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."],"tags":["control-record","deserialization","kraft"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}