{"record":{"id":"e7abd4241cf70c36","repo":"aeron-io/aeron","slug":"length-must-be-positive","errorCode":null,"errorMessage":"length must be positive","messagePattern":"length must be positive","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"aeron-archive/src/main/java/io/aeron/archive/RecordingReader.java","lineNumber":86,"sourceCode":"        if (length < NULL_LENGTH)\n        {\n            throw new IllegalArgumentException(\"invalid length: \" + length);\n        }\n\n        this.archiveDir = archiveDir;\n        this.termLength = recordingSummary.termBufferLength;\n        this.segmentLength = recordingSummary.segmentFileLength;\n        this.recordingId = recordingSummary.recordingId;\n\n        final long startPosition = recordingSummary.startPosition;\n        final long fromPosition = position == NULL_POSITION ? startPosition : position;\n        final long maxLength = recordingSummary.stopPosition != NULL_POSITION ?\n            recordingSummary.stopPosition - fromPosition : Long.MAX_VALUE - fromPosition;\n\n        final long replayLength = length == NULL_LENGTH ? maxLength : Math.min(length, maxLength);\n        if (replayLength < 0)\n        {\n            throw new IllegalArgumentException(\"length must be positive\");\n        }\n\n        final int positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);\n        final long startTermBasePosition = startPosition - (startPosition & (termLength - 1));\n        final int segmentOffset = (int)(fromPosition - startTermBasePosition) & (segmentLength - 1);\n        final int termId = ((int)(fromPosition >> positionBitsToShift) + recordingSummary.initialTermId);\n\n        segmentFilePosition = segmentFileBasePosition(startPosition, fromPosition, termLength, segmentLength);\n        openRecordingSegment();\n\n        termOffset = (int)(fromPosition & (termLength - 1));\n        termBaseSegmentOffset = segmentOffset - termOffset;\n        termBuffer = new UnsafeBuffer(mappedSegmentBuffer, termBaseSegmentOffset, termLength);\n\n        if (fromPosition > startPosition &&\n            (DataHeaderFlyweight.termOffset(termBuffer, termOffset) != termOffset ||\n            DataHeaderFlyweight.termId(termBuffer, termOffset) != termId ||\n            DataHeaderFlyweight.streamId(termBuffer, termOffset) != recordingSummary.streamId))","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-archive/src/main/java/io/aeron/archive/RecordingReader.java#L68-L104","documentation":"After clamping the requested length against what actually exists (stopPosition - fromPosition), if the resulting replayLength is negative the requested replay range is invalid (e.g. fromPosition beyond the recording's stopPosition with a positive length), so IllegalArgumentException is thrown.","triggerScenarios":"Requesting a replay whose fromPosition lies after the recording's stopPosition so that stopPosition - fromPosition plus the requested length yields a negative replayLength; passing NULL_LENGTH while fromPosition already exceeds stopPosition.","commonSituations":"Replaying a still-active recording with a position past the current stop; using a stale stopPosition snapshot; off-by-one in client-computed replay windows; replaying after the recording was truncated/rolled back.","solutions":["Clamp fromPosition to at most the recording's current stopPosition before requesting replay.","Fetch a fresh RecordingSummary/recording descriptor instead of a cached stopPosition.","Compute the replay window as [fromPosition, min(stopPosition, fromPosition+length)] client-side.","If replaying a live recording, poll for progress and adjust the window."],"exampleFix":"// before\nlong from = stopPosition + 1024; // beyond end\nreader = new RecordingReader(summary, dir, from, length);\n// after\nlong from = Math.min(requestedFrom, summary.stopPosition);\nreader = new RecordingReader(summary, dir, from, length);","handlingStrategy":"validation","validationCode":"long stop = summary.stopPosition != Aeron.NULL_POSITION ? summary.stopPosition : Long.MAX_VALUE;\nif (fromPosition > stop) fromPosition = stop; // clamp before replay","typeGuard":null,"tryCatchPattern":"try { replay(recId, from, len); }\ncatch (IllegalArgumentException e) {\n    if (e.getMessage().equals(\"length must be positive\")) {\n        from = Math.min(from, fetchStopPosition(recId));\n    }\n}","preventionTips":["Fetch a fresh recording summary rather than caching stopPosition.","Clamp replay windows to [0, currentStopPosition].","For live recordings, adapt the window as the recording progresses."],"tags":["replay","range","invalid-argument","archive"],"backgroundTag":"argument-out-of-range","analyzedSha":"6d60124e15e35c11b49ba2e3c2c2858a09a18803","analyzedAt":"2026-09-12T11:17:07.683Z","contentChangedAt":"2026-09-12T11:17:07.683Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}