aeron-io/aeron · error · IllegalArgumentException

Subscription URI must have 'control-mode=manual' uri=

Error message

Subscription URI must have 'control-mode=manual' uri=<channel>

What it means

ReplayMerge requires the subscription's channel URI to contain 'control-mode=manual' so the merge can control when the image joins the live destination. If the subscription URI lacks this parameter, the constructor throws IllegalArgumentException with the offending URI.

Solutions

  1. Add 'control-mode=manual' to the subscription URI before creating the subscription and ReplayMerge
  2. Create a fresh subscription with the corrected URI rather than reusing an auto-control-mode one
  3. Verify the URI with ChannelUri to confirm the parameter is present

Example fix

// before
Subscription sub = aeron.addSubscription("aeron:udp?endpoint=0.0.0.0:2000", streamId);
// after
Subscription sub = aeron.addSubscription("aeron:udp?endpoint=0.0.0.0:2000|control-mode=manual", streamId);
Defensive patterns

Strategy: validation

Validate before calling

if (!sub.channel().contains("control-mode=manual")) {
    throw new IllegalArgumentException("subscription URI must contain control-mode=manual: " + sub.channel());
}

Try / catch

try { new ReplayMerge(archive, sub, replayChannel, replayDest, liveDest, recordingId, 0); }
catch (IllegalArgumentException e) { if (e.getMessage().contains("control-mode=manual")) { recreateSubscriptionWithManualControlMode(); } }

Prevention

When it happens

Trigger: Calling new ReplayMerge(...) with a subscription created from a URI missing 'control-mode=manual', e.g. 'aeron:udp?endpoint=host:port'.

Common situations: Reusing an existing subscription that used automatic control mode; forgetting that ReplayMerge needs manual MDC control; older samples/docs showing plain endpoints.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of aeron-io/aeron@6d60124e15 (2026-09-12). Data as JSON: /api/errors/7c4264b9b6a6c37c. Report an issue: GitHub.

Appendix: source

Thrown at aeron-archive/src/main/java/io/aeron/archive/client/ReplayMerge.java:133

        final String replayChannel,
        final String replayDestination,
        final String liveDestination,
        final long recordingId,
        final long startPosition,
        final EpochClock epochClock,
        final long mergeProgressTimeoutMs)
    {
        if (subscription.channel().startsWith(IPC_CHANNEL) ||
            replayChannel.startsWith(IPC_CHANNEL) ||
            replayDestination.startsWith(IPC_CHANNEL) ||
            liveDestination.startsWith(IPC_CHANNEL))
        {
            throw new IllegalArgumentException("IPC merging is not supported");
        }

        if (!subscription.channel().contains("control-mode=manual"))
        {
            throw new IllegalArgumentException(
                "Subscription URI must have 'control-mode=manual' uri=" + subscription.channel());
        }

        this.archive = archive;
        this.subscription = subscription;
        this.epochClock = epochClock;
        this.replayDestination = replayDestination;
        this.liveDestination = liveDestination;
        this.recordingId = recordingId;
        this.startPosition = startPosition;
        this.mergeProgressTimeoutMs = mergeProgressTimeoutMs;

        replayChannelUri = ChannelUri.parse(replayChannel);
        replayChannelUri.put(CommonContext.LINGER_PARAM_NAME, "0");
        replayChannelUri.put(CommonContext.EOS_PARAM_NAME, "false");

        final String replayEndpoint = ChannelUri.parse(replayDestination).get(ENDPOINT_PARAM_NAME);
        if (replayEndpoint.endsWith(":0"))

View on GitHub (pinned to 6d60124e15)