aeron-io/aeron · error · ArchiveException

errorCode (variable)

errorCode (variable)

Error message

errorMessage + ", recordingId=" + recordingId + ", replaySessionId=" + sessionId + ", segmentFile=" + segmentFileName(recordingId, segmentFileBasePosition)

What it means

ReplaySession.raiseError is the central failure point for a replay session: it attaches the errorCode plus recordingId, replaySessionId and the current segment file name to the message, transitions the session to INACTIVE, and throws ArchiveException with the original cause. It is a generic error-raising path used by init, replay, doWork, verifyChecksum and openRecordingSegment, so the appended context is the key to diagnosing which stage failed.

Solutions

  1. Read the full errorMessage (errorCode, recordingId, replaySessionId, segmentFile) to identify which replay stage failed and fix accordingly
  2. If replay position was rejected, clamp the requested position/replayLength to the recording descriptor's startPosition/stopPosition obtained from the archive
  3. If a segment file is missing/incomplete, wait for recording to progress (or stop) before replaying, and verify the archive directory is intact
  4. Check archive logs and disk integrity for the named segment file; restore or re-record if the file is corrupt

Example fix

// before: replay from an unvalidated position
archive.startReplay(recordingId, requestedPosition, length);

// after: validate against the recording descriptor first
RecordingDescriptor d = archive.listRecording(recordingId);
long pos = Math.max(requestedPosition, d.startPosition());
long len = Math.min(length, d.stopPosition() - pos);
archive.startReplay(recordingId, pos, len);
Defensive patterns

Strategy: try-catch

Validate before calling

RecordingDescriptor d = archive.listRecording(recordingId);
if (replayPosition < d.startPosition() || replayPosition > d.stopPosition()) {
    throw new IllegalArgumentException("replay position out of range: " + replayPosition);
}

Type guard

static boolean replayPositionValid(RecordingDescriptor d, long position, long length) {
    return position >= d.startPosition() && position + length <= d.stopPosition();
}

Try / catch

try {
    replaySubscription = archive.startReplay(recordingId, position, length, replayChannel, replayStreamId);
} catch (ArchiveException e) {
    if (e.getMessage().contains("replaySessionId=")) {
        // parse errorCode and segmentFile from message; clamp position or wait for recording progress
        retryReplayWithAdjustedPosition(e.errorCode());
    } else { throw e; }
}

Prevention

When it happens

Trigger: Any replay-session stage that validates state and calls raiseError: replay requested at an invalid position, replay length/position out of range, replaying a recording not extended to the requested stop position, a segment file missing or failing checksum verification, or a failed segment open. The concrete errorCode/cause on the ArchiveException identifies the sub-case.

Common situations: Client requests a replay from a position before the recording's startPosition or beyond its stopPosition; requesting replay of a still-active recording whose segments are not yet on disk; corrupted or partially written segment files causing checksum verification failures; using a stale sessionId after the replay session was already torn down.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at aeron-archive/src/main/java/io/aeron/archive/ReplaySession.java:541

            }

            position += bytesRead;
        }
        while (byteBuffer.remaining() > 0);

        return limit;
    }

    private void raiseError(final String errorMessage, final int errorCode, final Throwable cause)
    {
        revokePublication = true;
        this.errorCode = errorCode;
        this.errorMessage = errorMessage +
            ", recordingId=" + recordingId +
            ", replaySessionId=" + sessionId +
            ", segmentFile=" + segmentFileName(recordingId, segmentFileBasePosition);
        state(State.INACTIVE, errorMessage);
        throw new ArchiveException(this.errorMessage, cause, errorCode);
    }

    private boolean notExtended(final long replayPosition, final long oldStopPosition)
    {
        final Counter limitPosition = this.limitPosition;
        final long currentLimitPosition = limitPosition.get();
        long newStopPosition = oldStopPosition;

        if (limitPosition.isClosed())
        {
            if (countersReader.getCounterRegistrationId(limitPosition.id()) == limitPosition.registrationId())
            {
                replayLimit = currentLimitPosition;
                newStopPosition = Math.max(oldStopPosition, currentLimitPosition);
            }
            else if (replayLimit >= oldStopPosition)
            {
                replayLimit = oldStopPosition;

View on GitHub (pinned to 6d60124e15)