apache/kafka · error · InvalidRecordException
Invalid record size: expected {} bytes in record payload, bu
Error message
Invalid record size: expected {} bytes in record payload, but instead the buffer has only {} remaining bytes. What it means
Thrown by DefaultRecord.readFrom(ByteBuffer, ...) when the varint-prefixed body length declares more bytes than the buffer actually has remaining. It is an InvalidRecordException surfaced during magic-v2 record decoding: the on-wire size prefix is inconsistent with the bytes the caller handed to the parser. The library throws it to refuse to manufacture a record out of a truncated or rewritten batch.
Source
Thrown at clients/src/main/java/org/apache/kafka/common/record/internal/DefaultRecord.java:313
public static DefaultRecord readFrom(ByteBuffer buffer,
long baseOffset,
long baseTimestamp,
int baseSequence,
Long logAppendTime) {
int sizeOfBodyInBytes = ByteUtils.readVarint(buffer);
return readFrom(buffer, sizeOfBodyInBytes, baseOffset, baseTimestamp,
baseSequence, logAppendTime);
}
private static DefaultRecord readFrom(ByteBuffer buffer,
int sizeOfBodyInBytes,
long baseOffset,
long baseTimestamp,
int baseSequence,
Long logAppendTime) {
if (buffer.remaining() < sizeOfBodyInBytes)
throw new InvalidRecordException("Invalid record size: expected " + sizeOfBodyInBytes +
" bytes in record payload, but instead the buffer has only " + buffer.remaining() +
" remaining bytes.");
try {
int recordStart = buffer.position();
byte attributes = buffer.get();
long timestampDelta = ByteUtils.readVarlong(buffer);
long timestamp = baseTimestamp + timestampDelta;
if (logAppendTime != null)
timestamp = logAppendTime;
int offsetDelta = ByteUtils.readVarint(buffer);
long offset = baseOffset + offsetDelta;
int sequence = baseSequence >= 0 ?
DefaultRecordBatch.incrementSequence(baseSequence, offsetDelta) :
RecordBatch.NO_SEQUENCE;
// read key
int keySize = ByteUtils.readVarint(buffer);View on GitHub (pinned to c31c9215e1)
Solutions
- Inspect the caller that produced the ByteBuffer (e.g. DefaultRecordBatch, MemoryRecords, or a custom Decoder) and confirm the slice handed to DefaultRecord.readFrom spans the full declared batch length.
- Verify the broker and client are on compatible Kafka versions and both use record-batch magic v2 (introduced in 0.11.0); downgrade or upgrade together.
- If reading from a log segment, run kafka-dump-log on the segment to confirm the size prefix matches the payload, and restore from a replica if the segment is truncated.
- Reproduce with a unit test using DefaultRecord.writeTo on the same key/value/headers and diff the produced bytes against the input to locate the size mismatch.
Example fix
// before: caller slices a buffer shorter than the declared body
int bodySize = ByteUtils.readVarint(buf);
DefaultRecord r = DefaultRecord.readFrom(buf, baseOffset, baseTimestamp, baseSequence, lat);
// after: slice exactly bodySize bytes for the inner record, validate first
int bodySize = ByteUtils.readVarint(buf);
if (buf.remaining() < bodySize) {
throw new InvalidRecordException("Truncated batch: need " + bodySize + ", have " + buf.remaining());
}
DefaultRecord r = DefaultRecord.readFrom(buf, baseOffset, baseTimestamp, baseSequence, lat); Defensive patterns
Strategy: try-catch
Validate before calling
// If you hold the ByteBuffer and the declared body size yourself:
int sizeOfBodyInBytes = ByteUtils.readVarint(buffer);
if (buffer.remaining() < sizeOfBodyInBytes) {
// truncated frame: do not call DefaultRecord.readFrom(...); log and skip
return;
} Type guard
// Narrow to a readable record slice before delegating.
private static boolean isReadableRecordSlice(ByteBuffer b, int sizeOfBodyInBytes) {
return b != null && b.remaining() >= sizeOfBodyInBytes && sizeOfBodyInBytes >= 0;
} Try / catch
try {
DefaultRecord r = DefaultRecord.readFrom(buffer, baseOffset, baseTimestamp, baseSequence, logAppendTime);
} catch (InvalidRecordException e) {
// corrupt/truncated record payload; advance past it or skip the batch
LOG.warn("Skipping malformed record at offset {}", baseOffset, e);
} Prevention
- Never hand a ByteBuffer to readFrom that has fewer bytes than the varint-declared body size.
- When reading from a stream, read exactly sizeOfBodyInBytes bytes (Utils.readFully) and verify the returned count before flipping the buffer.
- Treat any buffer returned by the broker as untrusted; validate frame length at the batch level (DefaultRecordBatch) before descending into records.
- If you broker records yourself, enable CRC32C checks on the batch so truncation is caught upstream.
When it happens
Trigger: Raised at DefaultRecord.java:312-315 when the per-record body size (read via ByteUtils.readVarint at line 301) exceeds buffer.remaining() at the moment of the second private readFrom entry. Hit whenever a caller slices a ByteBuffer too short (e.g. DefaultRecordBatch iterating past its declared batch length), or when an upstream caller hands in a buffer whose limit was manually trimmed.
Common situations: Corruption of an in-memory batch after a partial network read, a custom serializer that emits the wrong size prefix, a producer/consumer client version mismatch where the wire format changed (pre-magic-v2 vs v2), or a log segment that was truncated mid-record after a hard broker crash. Also seen with interceptors/clamps that rewrite batches without recomputing the size varint.
Related errors
- Found invalid number of record headers {}
- Found invalid number of record headers. {} is larger than th
- Invalid record size: expected to read {} bytes in record pay
- Found invalid record structure
- Invalid negative header key size {}
AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03).
Data as JSON: /data/errors/88f0ee5009cd4abf.json.
Report an issue: GitHub.