aeron-io/aeron · error · IllegalStateException
ReplayMerge Image closed unexpectedly.
Error message
ReplayMerge Image closed unexpectedly.
What it means
During catchup, ReplayMerge polls the replay image; if no new position progress is recorded in a poll and the image is closed, the library concludes the replay ended abnormally (rather than reaching the merge position) and throws IllegalStateException 'ReplayMerge Image closed unexpectedly.'
Solutions
- Check archive logs for the replay session termination reason
- Retry the ReplayMerge from a new recording position once the archive is available
- Verify network stability to the replay destination
- Handle the exception and fall back to a fresh ArchiveClient.replay or full subscription
Example fix
// before: no recovery
merge.doWork(); // throws, aborts
// after
try { merge.doWork(); } catch (IllegalStateException e) { if (e.getMessage().contains("Image closed unexpectedly")) { reinitReplayMerge(); } } Defensive patterns
Strategy: try-catch
Validate before calling
if (image.isClosed()) { reinitializeReplayMerge(); } // check before polling each cycle Try / catch
try { merge.doWork(); }
catch (IllegalStateException e) { if (e.getMessage().contains("Image closed unexpectedly")) { recreateReplayMerge(recordingId); } } Prevention
- Monitor archive health and replay session state during merge
- Retry the merge with a fresh subscription after image closure
- Keep network paths to the archive replay destination reliable
When it happens
Trigger: The replay image is closed before the catchup position is reached — archive session terminated, archive connection dropped, or the replay was cancelled — while ReplayMerge.doWork runs catchup.
Common situations: Archive process restart or failover mid-catchup; network disruption between client and archive replay destination; recording ends/deleted causing session termination.
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
- ReplayMerge no progress: state=
- existing max write time counter detected for archiveId=
- ControlSession.RESPONSE_NOT_CONNECTED_MSG + ": " + session
- failed to fetch remote recording descriptor
- failed to resolve subscription endpoint: channel=" +…
AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12).
Data as JSON: /api/errors/f8a1bbe419270d18.
Report an issue: GitHub.
Appendix: source
Thrown at aeron-archive/src/main/java/io/aeron/archive/client/ReplayMerge.java:465
if (null != image)
{
final long position = image.position();
if (position >= nextTargetPosition)
{
timeOfLastProgressMs = nowMs;
positionOfLastProgress = position;
state(State.ATTEMPT_LIVE_JOIN);
workCount += 1;
}
else if (position > positionOfLastProgress)
{
timeOfLastProgressMs = nowMs;
positionOfLastProgress = position;
}
else if (image.isClosed())
{
throw new IllegalStateException("ReplayMerge Image closed unexpectedly.");
}
}
return workCount;
}
private int attemptLiveJoin(final long nowMs)
{
int workCount = 0;
if (NULL_VALUE == activeCorrelationId)
{
if (callGetMaxRecordedPosition(nowMs))
{
timeOfLastProgressMs = nowMs;
workCount += 1;
}
}View on GitHub (pinned to 6d60124e15)