aeron-io/aeron · error · ArchiveEvent

INVALID_EXTENSION

INVALID_EXTENSION

Error message

cannot extend recording ${recordingId} image.joinPosition=${joinPosition} != rec.stopPosition=${stopPosition}

What it means

Thrown by ArchiveConductor when a client requests extending an existing recording with a new publication whose Image joinPosition does not equal the recording's stored stopPosition. A valid extension must continue the recording exactly where it stopped, so any position gap or overlap is rejected with error code INVALID_EXTENSION. The archive guarantees that extended recordings remain a contiguous, gap-free stream.

Solutions

  1. Ensure the publication used to extend the recording joins at exactly the recorded stopPosition (same source, same stream, position continuity preserved).
  2. Verify you passed the correct recordingId and that no other publication has advanced the recording since it stopped.
  3. Re-record from the beginning instead of extending if position continuity cannot be guaranteed.
  4. Check that the extension publication is on the same channel/streamId as the original recording.

Example fix

// before
archive.extendRecording(recordingId, false, channel, streamId); // publication started fresh at position 0
// after
long expected = archive.listRecording(recordingId).stopPosition;
// only extend with an original publication whose joinPosition == expected stopPosition,
// otherwise start a new recording instead of extending
Defensive patterns

Strategy: validation

Validate before calling

RecordingSubscriptionDescriptor rec = archive.listRecording(recordingId);
Image image = /* subscription image for the extending publication */;
if (rec == null || image.joinPosition() != rec.stopPosition) {
    throw new IllegalStateException("joinPosition " + image.joinPosition() +
        " != stopPosition " + (rec == null ? "<missing>" : rec.stopPosition));
}

Try / catch

try {
    archive.extendRecording(recordingId, isOriginal, channel, streamId);
} catch (ArchiveException e) {
    if (e.errorCode() == ArchiveException.INVALID_EXTENSION) {
        // fall back to starting a new recording
        archive.startRecording(channel, streamId, isOriginal);
    }
}

Prevention

When it happens

Trigger: Calling AeronArchive.extendRecording (or extendRecordingChannel) with a publication whose Image joinPosition differs from the original recording's stopPosition, e.g. the original recording stopped mid-stream or the extending publication started at a different position.

Common situations: Extending a recording with a publication from a different source/stream than the original; the original recording was stopped and restarted by a separate publication that did not carry over the position; replay/extension across cluster snapshots or term-id drift; passing the wrong recordingId to extendRecording.

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/e59b068863686da7. Report an issue: GitHub.

Appendix: source

Thrown at aeron-archive/src/main/java/io/aeron/archive/ArchiveConductor.java:2166

                return subscription;
            }
        }

        return null;
    }

    private void validateImageForExtendRecording(
        final long correlationId,
        final ControlSession controlSession,
        final Image image,
        final RecordingSummary recordingSummary)
    {
        if (image.joinPosition() != recordingSummary.stopPosition)
        {
            final String msg = "cannot extend recording " + recordingSummary.recordingId +
                " image.joinPosition=" + image.joinPosition() + " != rec.stopPosition=" + recordingSummary.stopPosition;
            controlSession.sendErrorResponse(correlationId, INVALID_EXTENSION, msg);
            throw new ArchiveEvent(msg);
        }

        if (image.initialTermId() != recordingSummary.initialTermId)
        {
            final String msg = "cannot extend recording " + recordingSummary.recordingId +
                " image.initialTermId=" + image.initialTermId() +
                " != rec.initialTermId=" + recordingSummary.initialTermId;
            controlSession.sendErrorResponse(correlationId, INVALID_EXTENSION, msg);
            throw new ArchiveEvent(msg);
        }

        if (image.termBufferLength() != recordingSummary.termBufferLength)
        {
            final String msg = "cannot extend recording " + recordingSummary.recordingId +
                " image.termBufferLength=" + image.termBufferLength() +
                " != rec.termBufferLength=" + recordingSummary.termBufferLength;
            controlSession.sendErrorResponse(correlationId, INVALID_EXTENSION, msg);
            throw new ArchiveEvent(msg);

View on GitHub (pinned to 6d60124e15)