apache/kafka · error · ConfigException

cannot be set when group.protocol=

Error message

{invalidConfigs} cannot be set when group.protocol={groupProtocol}

What it means

Thrown by ConsumerConfig.checkUnsupportedConfigsPostProcess when one or more configs that are invalid for the selected group.protocol are present and non-blank. For the CONSUMER protocol, partition.assignment.strategy, heartbeat.interval.ms, session.timeout.ms and the share-mode configs are disallowed; for the CLASSIC protocol, group.remote.assignor and the share-mode configs are disallowed. The protocol-specific coordinator manages these internally.

Solutions

  1. Remove the offending keys listed in the error message from the consumer Properties.
  2. Switch group.protocol to the one matching the configs you need (e.g. classic if you require a custom partition assignor).
  3. Maintain a per-protocol config template and load only the relevant set.

Example fix

// before
props.put("group.protocol", "consumer");
props.put("partition.assignment.strategy", CooperativeStickyAssignor.class.getName());
props.put("heartbeat.interval.ms", "3000");
new KafkaConsumer<>(props);

// after
props.put("group.protocol", "consumer");
// partition.assignment.strategy, heartbeat.interval.ms, session.timeout.ms removed
new KafkaConsumer<>(props);
Defensive patterns

Strategy: validation

Validate before calling

String protocol = String.valueOf(configs.get(ConsumerConfig.GROUP_PROTOCOL_CONFIG));
List<String> forbidden = "CONSUMER".equalsIgnoreCase(protocol)
    ? List.of(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,
              ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG,
              ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG)
    : List.of("group.remote.assignor");
List<String> present = forbidden.stream().filter(configs::containsKey).toList();
if (!present.isEmpty()) {
    throw new ConfigException(present + " not allowed with group.protocol=" + protocol);
}

Type guard

static boolean groupProtocolConfigConsistent(Map<String,Object> c) {
    String p = String.valueOf(c.get(ConsumerConfig.GROUP_PROTOCOL_CONFIG));
    if ("CONSUMER".equalsIgnoreCase(p)) {
        return !c.containsKey(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG)
            && !c.containsKey(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG)
            && !c.containsKey(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG);
    }
    return true;
}

Try / catch

try {
    consumer = new KafkaConsumer<>(props);
} catch (ConfigException e) {
    if (e.getMessage().contains("cannot be set when group.protocol")) {
        // strip the offending keys and retry, or surface to the user
        throw e;
    }
    throw e;
}

Prevention

When it happens

Trigger: Setting group.protocol=consumer while also setting partition.assignment.strategy, heartbeat.interval.ms, or session.timeout.ms. Or setting group.protocol=classic while also setting group.remote.assignor. Any non-blank value (including 'null' string in some edge cases of user input) for these keys triggers the check.

Common situations: Migrating from classic to consumer protocol and forgetting to strip classic-only configs; copy-pasted consumer templates that always include assignor/heartbeat/session settings; tooling that injects these configs unconditionally.

Related errors


AI-assisted analysis of apache/kafka@996fb4585a (2026-08-11). Data as JSON: /api/errors/6b8f01ec34a3ad93. Report an issue: GitHub.

Appendix: source

Thrown at clients/src/main/java/org/apache/kafka/clients/consumer/ConsumerConfig.java:838

        String groupProtocol = getString(GROUP_PROTOCOL_CONFIG);
        if (GroupProtocol.CLASSIC.name().equalsIgnoreCase(groupProtocol)) {
            checkUnsupportedConfigsPostProcess(GroupProtocol.CLASSIC, CLASSIC_PROTOCOL_UNSUPPORTED_CONFIGS);
        } else if (GroupProtocol.CONSUMER.name().equalsIgnoreCase(groupProtocol)) {
            checkUnsupportedConfigsPostProcess(GroupProtocol.CONSUMER, CONSUMER_PROTOCOL_UNSUPPORTED_CONFIGS);
        }
    }

    private void checkUnsupportedConfigsPostProcess(GroupProtocol groupProtocol, List<String> unsupportedConfigs) {
        if (getString(GROUP_PROTOCOL_CONFIG).equalsIgnoreCase(groupProtocol.name())) {
            List<String> invalidConfigs = new ArrayList<>();
            unsupportedConfigs.forEach(configName -> {
                Object config = originals().get(configName);
                if (config != null && !Utils.isBlank(config.toString())) {
                    invalidConfigs.add(configName);
                }
            });
            if (!invalidConfigs.isEmpty()) {
                throw new ConfigException(String.join(", ", invalidConfigs) +
                        " cannot be set when " + GROUP_PROTOCOL_CONFIG + "=" + groupProtocol.name());
            }
        }
    }

    /**
     * Constructs a new ConsumerConfig with the given properties.
     *
     * @param props The consumer configuration properties
     */
    public ConsumerConfig(Properties props) {
        super(CONFIG, props);
    }

    /**
     * Constructs a new ConsumerConfig with the given properties.
     *
     * @param props The consumer configuration properties

View on GitHub (pinned to 996fb4585a)