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
- Ensure the publication used to extend the recording joins at exactly the recorded stopPosition (same source, same stream, position continuity preserved).
- Verify you passed the correct recordingId and that no other publication has advanced the recording since it stopped.
- Re-record from the beginning instead of extending if position continuity cannot be guaranteed.
- 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
- Always extend with the original publication that produced the recording.
- Query stopPosition via listRecording before extending and assert equality with image.joinPosition().
- Do not restart recordings with brand-new publications if you plan to extend later; use replay-extend for continuing streams.
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
- catalogFileSyncLevel
- invalid fileIoMaxLength=
- Archive.Context.controlChannel must be set
- Archive.Context.controlChannel must be UDP media: uri=
- local control channel must be IPC media: uri=
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)