{"id":"0e3d25b9e8cbc98e","repo":"apache/kafka","slug":"incorrect-declared-batch-size-records-still-remai","errorCode":null,"errorMessage":"Incorrect declared batch size, records still remaining in file","messagePattern":"Incorrect declared batch size, records still remaining in file","errorType":"exception","errorClass":"InvalidRecordException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/common/record/internal/DefaultRecordBatch.java","lineNumber":608,"sourceCode":"\n        @Override\n        public boolean hasNext() {\n            return readRecords < numRecords;\n        }\n\n        @Override\n        public Record next() {\n            if (readRecords >= numRecords)\n                throw new NoSuchElementException();\n\n            readRecords++;\n            Record rec = readNext(baseOffset, baseTimestamp, baseSequence, logAppendTime);\n            if (readRecords == numRecords) {\n                // Validate that the actual size of the batch is equal to declared size\n                // by checking that after reading declared number of items, there no items left\n                // (overflow case, i.e. reading past buffer end is checked elsewhere).\n                if (!ensureNoneRemaining())\n                    throw new InvalidRecordException(\"Incorrect declared batch size, records still remaining in file\");\n            }\n            return rec;\n        }\n\n        protected abstract Record readNext(long baseOffset, long baseTimestamp, int baseSequence, Long logAppendTime);\n\n        protected abstract boolean ensureNoneRemaining();\n\n        @Override\n        public void remove() {\n            throw new UnsupportedOperationException();\n        }\n\n    }\n\n    // visible for testing\n    abstract class StreamRecordIterator extends RecordIterator {\n        private final InputStream inputStream;","sourceCodeStart":590,"sourceCodeEnd":626,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/common/record/internal/DefaultRecordBatch.java#L590-L626","documentation":"Thrown by DefaultRecordBatch.RecordIterator.next() when, after iterating the declared record count for the batch, ensureNoneRemaining() reports bytes still left in the underlying buffer/stream. It is an integrity check that the batch's declared record count matches the actual payload length, guarding against truncated or padded batches whose size header is inconsistent with the serialized records. The library throws InvalidRecordException (not a generic IOException) so callers can classify it as a corrupt-record condition and skip/quarantine the batch rather than retry blindly.","triggerScenarios":"Iterating a DefaultRecordBatch (v2 magic) via its RecordIterator / streamingIterator where the batch's count() field undercounts the records actually encoded in the payload. Produced by calling forEach/iterator on a Records batch read from FileRecords or MemoryRecords, then the last next() triggers ensureNoneRemaining() which returns false because inputStream.read() != -1.","commonSituations":"On-disk log segment corruption (partial write, torn write after a broker crash, fsync gap), a bug in a custom serializer or producer interceptor that mis-writes the record count, reading a log file produced by an incompatible/forked Kafka build, or a partial segment recovery where the batch footer/size was rewritten but the count was not. Also seen after unsafe manual edits to .log segment files or when two segments were concatenated incorrectly.","solutions":["Inspect the segment with kafka-dump-log.sh (--index-sanity-check true --deep-iteration) on the affected .log file to confirm the batch with the mismatched count and its offset.","If corruption is isolated, truncate the segment past the last valid offset and let replication/leadership migration re-replicate from an in-sync replica; verify the topic's min.insync.replicas before doing so.","If the count mismatch is from a producer/serializer bug, reproduce locally with a unit test asserting batch.count() against the serialized payload, fix the writer, and re-produce the affected messages.","Enable log.flush.interval.messages / log.flush.interval.ms tuning only if the root cause was a torn write from improper flush/fsync behavior; otherwise leave defaults."],"exampleFix":"// before: custom batch writer that writes records then patches a wrong count\nint declared = buffer.getInt(COUNT_OFFSET);\n// after: compute count from actual records written, write once\nbuffer.putInt(COUNT_OFFSET, recordsWritten);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  for (Record r : batch) { /* process */ }\n} catch (org.apache.kafka.common.InvalidRecordException e) {\n  // Declared record count under-counts actual bytes: batch is corrupt.\n  // Quarantine the segment, record batch.baseOffset(), skip to next batch.\n  log.warn(\"Corrupt batch at offset {}: size mismatch\", batch.baseOffset(), e);\n}","preventionTips":["Treat InvalidRecordException as unrecoverable for that batch: do not retry the same bytes, skip or seek past the batch.","Enable broker-side CRC32C validation (unclean.leader.shutdown / log validation) so corrupt batches are rejected before persisting.","Run kafka-dump-log --files <segment> --deep-iteration to audit segments suspected of corruption before consumer reads.","Ensure producers wait for full acks (acks=all) and the broker fsyncs to avoid partial/truncated batch writes on crash.","Monitor disk health (SMART errors, EIO) — size-mismatch corruption is most often a storage or OS-crash artifact."],"tags":["kafka","records","corruption","default-record-batch","broker"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}