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

  1. 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.
  2. 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.
  3. 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.
  4. 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

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


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