aeron-io/aeron · error · IllegalArgumentException

fromPosition + " position not aligned to valid fragment

Error message

fromPosition + " position not aligned to valid fragment

What it means

When replay starts after the recording's startPosition, the constructor checks that fromPosition points at a valid fragment by comparing the data header's termOffset, termId and streamId at that location. If fromPosition is not aligned to an actual fragment boundary (or belongs to a different stream), the reader closes its resources and throws IllegalArgumentException.

Solutions

  1. Resume replays only from positions previously reported as fragment boundaries (e.g. last position from a previous replay or recording progress).
  2. Ensure the streamId in the replay request matches the recorded stream.
  3. Use boundedReplay/replay with NULL_POSITION to start from the beginning instead of guessing offsets.
  4. Capture positions via the recording events/recording descriptor APIs rather than manual arithmetic.

Example fix

// before
long resumeAt = lastKnownPosition + 123; // arbitrary offset
// after
long resumeAt = lastFragmentPosition; // position previously reported by replay/callbacks
archive.startReplay(recordingId, resumeAt, Aeron.NULL_LENGTH, channel, streamId);
Defensive patterns

Strategy: validation

Validate before calling

// only resume from positions that were reported as fragment boundaries
if (resumePosition > startPosition && !positionsFromCallbacks.contains(resumePosition)) {
    resumePosition = Aeron.NULL_POSITION; // fall back to start
}

Try / catch

try { replay(recId, from, len); }
catch (IllegalArgumentException e) {
    if (e.getMessage().endsWith("position not aligned to valid fragment")) {
        replayFromLastKnownBoundary(recId);
    }
}

Prevention

When it happens

Trigger: Requesting a replay from a position that was never a fragment start — an arbitrary byte offset, a position from a different recording/stream, or a padded/batch boundary rather than a fragment boundary.

Common situations: Client guessed a resume position instead of tracking the last received fragment position; reusing positions recorded from a different streamId; resuming after archive restart with mismatched term metadata.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

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

        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))
        {
            close();
            throw new IllegalArgumentException(fromPosition + " position not aligned to valid fragment");
        }

        replayPosition = fromPosition;
        replayLimit = fromPosition + replayLength;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close()
    {
        closeRecordingSegment();
    }

    long replayPosition()
    {
        return replayPosition;

View on GitHub (pinned to 6d60124e15)