{"id":"3610ff4f0086c17b","repo":"apache/kafka","slug":"slice-from-position-exceeds-end-position-of","errorCode":null,"errorMessage":"Slice from position {} exceeds end position of {}","messagePattern":"Slice from position (.+?) exceeds end position of (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/common/record/internal/FileRecords.java","lineNumber":175,"sourceCode":"     * @param position The start position to begin the read from\n     * @param size The number of bytes after the start position to include\n     * @return A unaligned slice of records on this message set limited based on the given position and size\n     */\n    public UnalignedFileRecords sliceUnaligned(int position, int size) {\n        int availableBytes = availableBytes(position, size);\n        return new UnalignedFileRecords(channel, this.start + position, availableBytes);\n    }\n\n    private int availableBytes(int position, int size) {\n        // Cache current size in case concurrent write changes it\n        int currentSizeInBytes = sizeInBytes();\n\n        if (position < 0)\n            throw new IllegalArgumentException(\"Invalid position: \" + position + \" in read from \" + this);\n        // position should always be relative to the start of the file hence compare with file size\n        // to verify if the position is within the file.\n        if (position > currentSizeInBytes)\n            throw new IllegalArgumentException(\"Slice from position \" + position + \" exceeds end position of \" + this);\n        if (size < 0)\n            throw new IllegalArgumentException(\"Invalid size: \" + size + \" in read from \" + this);\n\n        int end = this.start + position + size;\n        // Handle integer overflow or if end is beyond the end of the file\n        if (end < 0 || end > start + currentSizeInBytes)\n            end = this.start + currentSizeInBytes;\n        return end - (this.start + position);\n    }\n\n    /**\n     * Append a set of records to the file. This method is not thread-safe and must be\n     * protected with a lock.\n     *\n     * @param records The records to append\n     * @return the number of bytes written to the underlying file\n     */\n    public int append(MemoryRecords records) throws IOException {","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/common/record/internal/FileRecords.java#L157-L193","documentation":"Thrown by FileRecords.availableBytes when the requested slice start position exceeds the segment's current sizeInBytes. Because size is read from a AtomicInteger that can change under concurrent appends, the check uses a cached snapshot; a position past the end has no bytes to read. This protects against reads of data that was never written.","triggerScenarios":"Calling slice(position, size) or sliceUnaligned(position, size) where position > sizeInBytes(). Common during fetch handling when the highWatermark or lastStableOffset advances slower than the fetch offset the client requested, or during log truncation racing with an in-flight read.","commonSituations":"Consumer/replica fetch asks for bytes beyond the segment end after a truncation or before the leader has appended them. Also seen after manual segment manipulation (deleting bytes from a file without updating metadata) or when a Log offset-index points past the real file size.","solutions":["Re-check the segment's actual sizeInBytes (or FileChannel.size()) at the call site and recompute the slice position against it.","If racing with truncation, serialize reads against the log lock so position is validated against a stable size snapshot.","Validate the offset-index file (.index) matches the .log file size; rebuild indexes with kafka-dump-log or the broker's index recovery if they diverge.","For raft snapshot slicing, ensure the snapshot size has been flushed before issuing sliceUnaligned."],"exampleFix":"// before: position taken from a stale index entry\nint pos = index.lookup(targetOffset).position;\nreturn records.slice(pos, fetchSize);\n\n// after: bound position against live size\nint pos = Math.min(index.lookup(targetOffset).position, records.sizeInBytes());\nreturn records.slice(pos, fetchSize);","handlingStrategy":"validation","validationCode":"int currentSize = records.sizeInBytes();\nif (position > currentSize) {\n    throw new IllegalArgumentException(\n        \"slice position \" + position + \" is past end \" + currentSize);\n}\nFileRecords slice = records.slice(position, size);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Cache records.sizeInBytes() into a local before computing positions, since concurrent appends change it.","Only slice at positions returned by searchForOffsetFromPosition / batch iteration.","Hold the segment lock while slicing if appends are concurrent."],"tags":["file-records","slice","bounds","fetch","replication"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}