{"record":{"id":"f8a1bbe419270d18","repo":"aeron-io/aeron","slug":"replaymerge-image-closed-unexpectedly","errorCode":null,"errorMessage":"ReplayMerge Image closed unexpectedly.","messagePattern":"ReplayMerge Image closed unexpectedly\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"aeron-archive/src/main/java/io/aeron/archive/client/ReplayMerge.java","lineNumber":465,"sourceCode":"\n        if (null != image)\n        {\n            final long position = image.position();\n            if (position >= nextTargetPosition)\n            {\n                timeOfLastProgressMs = nowMs;\n                positionOfLastProgress = position;\n                state(State.ATTEMPT_LIVE_JOIN);\n                workCount += 1;\n            }\n            else if (position > positionOfLastProgress)\n            {\n                timeOfLastProgressMs = nowMs;\n                positionOfLastProgress = position;\n            }\n            else if (image.isClosed())\n            {\n                throw new IllegalStateException(\"ReplayMerge Image closed unexpectedly.\");\n            }\n        }\n\n        return workCount;\n    }\n\n    private int attemptLiveJoin(final long nowMs)\n    {\n        int workCount = 0;\n\n        if (NULL_VALUE == activeCorrelationId)\n        {\n            if (callGetMaxRecordedPosition(nowMs))\n            {\n                timeOfLastProgressMs = nowMs;\n                workCount += 1;\n            }\n        }","sourceCodeStart":447,"sourceCodeEnd":483,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-archive/src/main/java/io/aeron/archive/client/ReplayMerge.java#L447-L483","documentation":"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.'","triggerScenarios":"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.","commonSituations":"Archive process restart or failover mid-catchup; network disruption between client and archive replay destination; recording ends/deleted causing session termination.","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"],"exampleFix":"// before: no recovery\nmerge.doWork(); // throws, aborts\n// after\ntry { merge.doWork(); } catch (IllegalStateException e) { if (e.getMessage().contains(\"Image closed unexpectedly\")) { reinitReplayMerge(); } }","handlingStrategy":"try-catch","validationCode":"if (image.isClosed()) { reinitializeReplayMerge(); } // check before polling each cycle","typeGuard":null,"tryCatchPattern":"try { merge.doWork(); }\ncatch (IllegalStateException e) { if (e.getMessage().contains(\"Image closed unexpectedly\")) { recreateReplayMerge(recordingId); } }","preventionTips":["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"],"tags":["replay-merge","image-closed","network","state"],"backgroundTag":"invalid-state-transition","analyzedSha":"6d60124e15e35c11b49ba2e3c2c2858a09a18803","analyzedAt":"2026-09-12T11:17:07.683Z","contentChangedAt":"2026-09-12T11:17:07.683Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}