aeron-io/aeron · error · IllegalArgumentException

length must be positive

Error message

length must be positive

What it means

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.

Solutions

  1. Clamp fromPosition to at most the recording's current stopPosition before requesting replay.
  2. Fetch a fresh RecordingSummary/recording descriptor instead of a cached stopPosition.
  3. Compute the replay window as [fromPosition, min(stopPosition, fromPosition+length)] client-side.
  4. If replaying a live recording, poll for progress and adjust the window.

Example fix

// before
long from = stopPosition + 1024; // beyond end
reader = new RecordingReader(summary, dir, from, length);
// after
long from = Math.min(requestedFrom, summary.stopPosition);
reader = new RecordingReader(summary, dir, from, length);
Defensive patterns

Strategy: validation

Validate before calling

long stop = summary.stopPosition != Aeron.NULL_POSITION ? summary.stopPosition : Long.MAX_VALUE;
if (fromPosition > stop) fromPosition = stop; // clamp before replay

Try / catch

try { replay(recId, from, len); }
catch (IllegalArgumentException e) {
    if (e.getMessage().equals("length must be positive")) {
        from = Math.min(from, fetchStopPosition(recId));
    }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12). Data as JSON: /api/errors/e7abd4241cf70c36. Report an issue: GitHub.

Appendix: source

Thrown at aeron-archive/src/main/java/io/aeron/archive/RecordingReader.java:86

        if (length < NULL_LENGTH)
        {
            throw new IllegalArgumentException("invalid length: " + length);
        }

        this.archiveDir = archiveDir;
        this.termLength = recordingSummary.termBufferLength;
        this.segmentLength = recordingSummary.segmentFileLength;
        this.recordingId = recordingSummary.recordingId;

        final long startPosition = recordingSummary.startPosition;
        final long fromPosition = position == NULL_POSITION ? startPosition : position;
        final long maxLength = recordingSummary.stopPosition != NULL_POSITION ?
            recordingSummary.stopPosition - fromPosition : Long.MAX_VALUE - fromPosition;

        final long replayLength = length == NULL_LENGTH ? maxLength : Math.min(length, maxLength);
        if (replayLength < 0)
        {
            throw new IllegalArgumentException("length must be positive");
        }

        final int positionBitsToShift = LogBufferDescriptor.positionBitsToShift(termLength);
        final long startTermBasePosition = startPosition - (startPosition & (termLength - 1));
        final int segmentOffset = (int)(fromPosition - startTermBasePosition) & (segmentLength - 1);
        final int termId = ((int)(fromPosition >> positionBitsToShift) + recordingSummary.initialTermId);

        segmentFilePosition = segmentFileBasePosition(startPosition, fromPosition, termLength, segmentLength);
        openRecordingSegment();

        termOffset = (int)(fromPosition & (termLength - 1));
        termBaseSegmentOffset = segmentOffset - termOffset;
        termBuffer = new UnsafeBuffer(mappedSegmentBuffer, termBaseSegmentOffset, termLength);

        if (fromPosition > startPosition &&
            (DataHeaderFlyweight.termOffset(termBuffer, termOffset) != termOffset ||
            DataHeaderFlyweight.termId(termBuffer, termOffset) != termId ||
            DataHeaderFlyweight.streamId(termBuffer, termOffset) != recordingSummary.streamId))

View on GitHub (pinned to 6d60124e15)