{"record":{"id":"b79745a629cb3bc9","repo":"aeron-io/aeron","slug":"replaychannel-must-be-set","errorCode":null,"errorMessage":"replayChannel must be set","messagePattern":"replayChannel must be set","errorType":"validation","errorClass":"ConfigurationException","httpStatus":null,"severity":"error","filePath":"aeron-archive/src/main/java/io/aeron/archive/client/PersistentSubscription.java","lineNumber":1697,"sourceCode":"\n            if (Aeron.NULL_VALUE == recordingId)\n            {\n                throw new ConfigurationException(\"recordingId must be set\");\n            }\n\n            if (Aeron.NULL_VALUE == liveStreamId)\n            {\n                throw new ConfigurationException(\"liveStreamId must be set\");\n            }\n\n            if (Strings.isEmpty(liveChannel))\n            {\n                throw new ConfigurationException(\"liveChannel must be set\");\n            }\n\n            if (Strings.isEmpty(replayChannel))\n            {\n                throw new ConfigurationException(\"replayChannel must be set\");\n            }\n\n            if (Aeron.NULL_VALUE == replayStreamId)\n            {\n                throw new ConfigurationException(\"replayStreamId must be set\");\n            }\n\n            if (null == aeronArchiveContext)\n            {\n                throw new ConfigurationException(\"aeronArchiveContext must be set\");\n            }\n\n            if (null == listener)\n            {\n                listener = new NoOpPersistentSubscriptionListener();\n            }\n\n            if (0 > recordingId)","sourceCodeStart":1679,"sourceCodeEnd":1715,"githubUrl":"https://github.com/aeron-io/aeron/blob/6d60124e15e35c11b49ba2e3c2c2858a09a18803/aeron-archive/src/main/java/io/aeron/archive/client/PersistentSubscription.java#L1679-L1715","documentation":"PersistentSubscription.Context.conclude() requires a replayChannel URI: the channel used to replay the recorded stream from the archive. If the configured replayChannel is null or empty, conclude() throws ConfigurationException. The subscription needs both a live and a replay path by design.","triggerScenarios":"Calling conclude() without calling context.replayChannel(String), or passing null/empty. Typical when only the live channel/stream were configured or a replay-only parameter set was partially filled.","commonSituations":"Forgetting the replay channel while configuring a persistent (live-then-replay) subscription; null value read from application config; mixing up liveChannel/replayChannel setters so one is left unset.","solutions":["Set a valid channel URI via replayChannel(\"aeron:udp?endpoint=...\") before conclude().","If you do not need a distinct replay endpoint, reuse the live channel URI (with the replay streamId).","Verify application config wiring supplies both channels."],"exampleFix":"// before\nctx.liveChannel(\"aeron:udp?endpoint=localhost:10000\");\nctx.liveStreamId(5);\nctx.conclude(); // throws: replayChannel must be set\n\n// after\nctx.liveChannel(\"aeron:udp?endpoint=localhost:10000\");\nctx.liveStreamId(5);\nctx.replayChannel(\"aeron:udp?endpoint=localhost:10001\");\nctx.replayStreamId(7);\nctx.conclude();","handlingStrategy":"validation","validationCode":"if (Strings.isEmpty(ctx.replayChannel())) {\n    throw new IllegalArgumentException(\"replayChannel must be set before conclude()\");\n}","typeGuard":null,"tryCatchPattern":"try {\n    ctx.conclude();\n} catch (ConfigurationException e) {\n    // supply a default replay channel derived from the live channel\n}","preventionTips":["Default the replayChannel to the liveChannel URI when no separate replay endpoint is needed.","Validate both channels in a shared config-validation helper."],"tags":["aeron-archive","configuration","missing-required-argument"],"backgroundTag":"missing-required-config-field","analyzedSha":"6d60124e15e35c11b49ba2e3c2c2858a09a18803","analyzedAt":"2026-09-12T11:17:07.683Z","contentChangedAt":"2026-09-12T11:17:07.683Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}