apache/kafka · error · ConfigException

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

Error message

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

What it means

Thrown by ConsumerConfig.checkUnsupportedConfigsPostProcess when one or more config keys incompatible with the selected group.protocol are present. For CLASSIC protocol the forbidden keys are group.remote.assignor, share.acknowledgement.mode, share.acquire.mode; for CONSUMER (the new KIP-848 protocol) the forbidden keys also include partition.assignment.strategy, heartbeat.interval.ms, and session.timeout.ms (these are owned by the broker in the new protocol). It is a ConfigException raised at consumer construction.

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 c31c9215e1)

Solutions

  1. Remove the listed invalidConfigs from your consumer properties for the chosen group.protocol.
  2. If using group.protocol=consumer, drop partition.assignment.strategy, heartbeat.interval.ms, and session.timeout.ms (the broker manages them now).
  3. If you need classic-era tuning knobs (custom assignor, specific heartbeat/session timeouts), keep group.protocol=classic and remove the consumer-protocol-only configs.
  4. Remove share.acknowledgement.mode and share.acquire.mode unless you are constructing a KafkaShareConsumer.

Example fix

// before (group.protocol=consumer with classic configs)
props.put(ConsumerConfig.GROUP_PROTOCOL_CONFIG, "consumer");
props.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, CooperativeStickyAssignor.class.getName());
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, "3000");
// throws: partition.assignment.strategy, heartbeat.interval.ms cannot be set when group.protocol=CONSUMER

// after (pick one)
// Option A: stay on consumer protocol, drop classic configs
props.remove(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG);
props.remove(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG);

// Option B: keep classic tuning, use classic protocol
props.put(ConsumerConfig.GROUP_PROTOCOL_CONFIG, "classic");
Defensive patterns

Strategy: validation

Validate before calling

// Reject mutually-exclusive configs up front based on group.protocol:
String protocol = (String) props.get(ConsumerConfig.GROUP_PROTOCOL_CONFIG);
List<String> classicUnsupported = List.of(/* CLASSIC_PROTOCOL_UNSUPPORTED_CONFIGS entries, e.g. remote.assignor */);
List<String> consumerUnsupported = List.of(/* CONSUMER_PROTOCOL_UNSUPPORTED_CONFIGS entries */);
List<String> unsupported = GroupProtocol.CONSUMER.name().equalsIgnoreCase(protocol) ? consumerUnsupported
                         : GroupProtocol.CLASSIC.name().equalsIgnoreCase(protocol) ? classicUnsupported
                         : Collections.emptyList();
List<String> present = unsupported.stream()
    .filter(c -> props.get(c) != null && !props.get(c).toString().isBlank())
    .toList();
if (!present.isEmpty()) {
    throw new ConfigException(present + " cannot be set when group.protocol=" + protocol);
}

Try / catch

try {
    new KafkaConsumer<>(props);
} catch (ConfigException e) {
    if (e.getMessage().contains("cannot be set when group.protocol=")) {
        // strip the offending keys and retry, or fail fast with a clear app-level message
        props.keySet().removeIf(k -> e.getMessage().contains(k));
        new KafkaConsumer<>(props);
    } else throw e;
}

Prevention

When it happens

Trigger: Constructing a KafkaConsumer with 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; or including share-group configs (share.acknowledgement.mode, share.acquire.mode) on a non-share consumer.

Common situations: Migrating to the new consumer group protocol (KIP-848) without removing classic-era tuning knobs; reusing a large shared properties file across consumers with different protocols; copy-pasting tuning guides written for classic protocol onto a consumer.group.protocol=consumer config; accidentally setting share-consumer configs on a normal consumer.

Related errors


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