apache/kafka · error · IllegalArgumentException

`mode` must be non-null if `securityProtocol` is `${security

Error message

`mode` must be non-null if `securityProtocol` is `${securityProtocol}`

What it means

Thrown by `ChannelBuilders.requireNonNullMode` (invoked from the internal `create(...)` method) when `connectionMode` is null for SSL, SASL_SSL, or SASL_PLAINTEXT protocols. Those protocols are directional — they build different `SslChannelBuilder`/`SaslChannelBuilder` instances for CLIENT vs SERVER, including different JAAS context loading and ssl.client.auth override behavior — so a null mode has no sensible default. PLAINTEXT does not require a mode and skips the check.

Source

Thrown at clients/src/main/java/org/apache/kafka/common/network/ChannelBuilders.java:215

        if (listenerName == null)
            parsedConfigs = (Map<String, Object>) config.values();
        else
            parsedConfigs = config.valuesWithPrefixOverride(listenerName.configPrefix());

        config.originals().entrySet().stream()
            .filter(e -> !parsedConfigs.containsKey(e.getKey())) // exclude already parsed configs
            // exclude already parsed listener prefix configs
            .filter(e -> !(listenerName != null && e.getKey().startsWith(listenerName.configPrefix()) &&
                parsedConfigs.containsKey(e.getKey().substring(listenerName.configPrefix().length()))))
            // exclude keys like `{mechanism}.some.prop` if "listener.name." prefix is present and key `some.prop` exists in parsed configs.
            .filter(e -> !(listenerName != null && parsedConfigs.containsKey(e.getKey().substring(e.getKey().indexOf('.') + 1))))
            .forEach(e -> parsedConfigs.put(e.getKey(), e.getValue()));
        return parsedConfigs;
    }

    private static void requireNonNullMode(ConnectionMode connectionMode, SecurityProtocol securityProtocol) {
        if (connectionMode == null)
            throw new IllegalArgumentException("`mode` must be non-null if `securityProtocol` is `" + securityProtocol + "`");
    }

    public static KafkaPrincipalBuilder createPrincipalBuilder(Map<String, ?> configs,
                                                               KerberosShortNamer kerberosShortNamer,
                                                               SslPrincipalMapper sslPrincipalMapper) {
        Class<?> principalBuilderClass = (Class<?>) configs.get(BrokerSecurityConfigs.PRINCIPAL_BUILDER_CLASS_CONFIG);
        final KafkaPrincipalBuilder builder;

        if (principalBuilderClass == null || principalBuilderClass == DefaultKafkaPrincipalBuilder.class) {
            builder = new DefaultKafkaPrincipalBuilder(kerberosShortNamer, sslPrincipalMapper);
        } else if (KafkaPrincipalBuilder.class.isAssignableFrom(principalBuilderClass)) {
            builder = (KafkaPrincipalBuilder) Utils.newInstance(principalBuilderClass);
        } else {
            throw new InvalidConfigurationException("Type " + principalBuilderClass.getName() + " is not " +
                    "an instance of " + KafkaPrincipalBuilder.class.getName());
        }

        if (builder instanceof Configurable)

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Use the public `clientChannelBuilder(...)` or `serverChannelBuilder(...)` entry points — they always supply a non-null ConnectionMode.
  2. If you must call `create(...)` directly, pass `ConnectionMode.CLIENT` or `ConnectionMode.SERVER` explicitly.
  3. Add an assertion in your wrapper so a null mode fails earlier with a clearer message.

Example fix

// before (internal call)
ChannelBuilders.create(SecurityProtocol.SSL, null, contextType,
    config, listenerName, false, null, null, null, time, logContext, null);

// after
ChannelBuilders.clientChannelBuilder(SecurityProtocol.SSL,
    contextType, config, listenerName, clientSaslMechanism, time, logContext);
Defensive patterns

Strategy: try-catch

Validate before calling

// `mode` (ConnectionMode) is not a parameter of the public clientChannelBuilder/serverChannelBuilder
// factories; it is supplied internally. The only user-side check is to avoid reflection/private-API use:
if (securityProtocol == SecurityProtocol.SSL
        || securityProtocol == SecurityProtocol.SASL_SSL
        || securityProtocol == SecurityProtocol.SASL_PLAINTEXT) {
    // ensure you are using ChannelBuilders.clientChannelBuilder(...) or serverChannelBuilder(...),
    // both of which pass a non-null ConnectionMode. Do not call the private create(...) directly.
}

Try / catch

try {
    ChannelBuilders.clientChannelBuilder(
        securityProtocol, contextType, config, listenerName,
        clientSaslMechanism, time, logContext);
} catch (IllegalArgumentException e) {
    // "`mode` must be non-null if `securityProtocol` is ..." -- indicates non-public API misuse.
    throw new IllegalStateException("ChannelBuilder produced without a connection mode", e);
}

Prevention

When it happens

Trigger: Reaching `create(...)` for SSL/SASL_SSL/SASL_PLAINTEXT with `connectionMode == null`. The public `clientChannelBuilder` always passes `ConnectionMode.CLIENT` and `serverChannelBuilder` passes `ConnectionMode.SERVER`, so this is normally only hit by code that calls the package/private `create` method directly or by a future refactor that introduces a null mode path.

Common situations: Custom broker/client tooling that invokes the internal create() path with a null ConnectionMode. Test harnesses that construct ChannelBuilders reflectively. A bug in a fork or downstream distributor that bypasses the public client/server entry points.

Related errors


AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03). Data as JSON: /data/errors/29128b0b2c3c704e.json. Report an issue: GitHub.