apache/kafka · error · ConfigException

Cannot set transaction.timeout.ms when transaction.two.phase

Error message

Cannot set transaction.timeout.ms when transaction.two.phase.commit.enable is set to true. Transactions will not expire with two-phase commit enabled.

What it means

Thrown by ProducerConfig.postProcessAndValidateIdempotenceConfigs when transaction.two.phase.commit.enable=true is combined with an explicitly set transaction.timeout.ms. With two-phase commit (2PC), an external coordinator decides when to finalize a transaction rather than the broker's timeout, so the broker-side transaction.timeout.ms does not apply; allowing both would mislead users into thinking the broker will still abort stalled transactions. The check at ProducerConfig.java:696 fires only when both are present in originals.

Source

Thrown at clients/src/main/java/org/apache/kafka/clients/producer/ProducerConfig.java:697

        if (shouldDisableIdempotence) {
            configs.put(ENABLE_IDEMPOTENCE_CONFIG, false);
            idempotenceEnabled = false;
        }

        // validate `transaction.id` after validating idempotence dependant configs because `enable.idempotence` config might be overridden
        boolean userConfiguredTransactions = originalConfigs.containsKey(TRANSACTIONAL_ID_CONFIG);
        if (!idempotenceEnabled && userConfiguredTransactions) {
            throw new ConfigException("Cannot set a " + ProducerConfig.TRANSACTIONAL_ID_CONFIG + " without also enabling idempotence.");
        }

        // Validate that transaction.timeout.ms is not set when transaction.two.phase.commit.enable is true
        // In standard Kafka transactions, the broker enforces transaction.timeout.ms and aborts any
        // transaction that isn't completed in time. With two-phase commit (2PC), an external coordinator
        // decides when to finalize, so broker-side timeouts don't apply. Disallow using both.
        boolean enable2PC = this.getBoolean(TRANSACTION_TWO_PHASE_COMMIT_ENABLE_CONFIG);
        boolean userConfiguredTransactionTimeout = originalConfigs.containsKey(TRANSACTION_TIMEOUT_CONFIG);
        if (enable2PC && userConfiguredTransactionTimeout) {
            throw new ConfigException(
                "Cannot set " + ProducerConfig.TRANSACTION_TIMEOUT_CONFIG +
                " when " + ProducerConfig.TRANSACTION_TWO_PHASE_COMMIT_ENABLE_CONFIG +
                " is set to true. Transactions will not expire with two-phase commit enabled."
            );
        }
    }

    private static String parseAcks(String acksString) {
        try {
            return acksString.trim().equalsIgnoreCase("all") ? "-1" : Short.parseShort(acksString.trim()) + "";
        } catch (NumberFormatException e) {
            throw new ConfigException("Invalid configuration value for 'acks': " + acksString);
        }
    }

    static Map<String, Object> appendSerializerToConfig(Map<String, Object> configs,
            Serializer<?> keySerializer,
            Serializer<?> valueSerializer) {

View on GitHub (pinned to c31c9215e1)

Solutions

  1. Remove the `transaction.timeout.ms` setting from your producer config — under 2PC the external coordinator owns finalization timing.
  2. If you genuinely need a timeout, leave 2PC disabled and use the standard transactional producer.
  3. Audit config files, environment variables, and Spring Boot property sources for inherited transaction.timeout.ms entries.

Example fix

// before
props.put("transactional.id", "my-tx");
props.put("transaction.two.phase.commit.enable", "true");
props.put("transaction.timeout.ms", "60000");

// after
props.put("transactional.id", "my-tx");
props.put("transaction.two.phase.commit.enable", "true");
// transaction.timeout.ms removed; coordinator owns finalization
Defensive patterns

Strategy: validation

Validate before calling

// transaction.timeout.ms is forbidden when two-phase commit is enabled.
boolean twoPhase = Boolean.TRUE.equals(
    cfg.get(ProducerConfig.TRANSACTION_TWO_PHASE_COMMIT_ENABLE_CONFIG));
boolean timeoutSet = cfg.containsKey(ProducerConfig.TRANSACTION_TIMEOUT_CONFIG);
if (twoPhase && timeoutSet) {
    throw new IllegalStateException(
        "Do not set transaction.timeout.ms when transaction.two.phase.commit.enable=true");
}
// If enabling 2PC at runtime, explicitly remove any stale timeout:
if (twoPhase) cfg.remove(ProducerConfig.TRANSACTION_TIMEOUT_CONFIG);

Try / catch

try {
    producer = new KafkaProducer<>(cfg);
} catch (ConfigException e) {
    if (e.getMessage().contains("transaction.timeout.ms")) {
        cfg.remove(ProducerConfig.TRANSACTION_TIMEOUT_CONFIG);
        producer = new KafkaProducer<>(cfg);
    } else throw e;
}

Prevention

When it happens

Trigger: User provides both `transaction.two.phase.commit.enable=true` and `transaction.timeout.ms=<value>` in the producer config. The originals().containsKey guard means a default value alone does not trigger it — only an explicit user-set timeout does.

Common situations: Adopting the two-phase-commit feature while copying a full producer config template that includes transaction.timeout.ms (e.g. 60000) from a standard transactional setup; or migrating a standard EOS producer to 2PC without pruning the timeout config.

Related errors


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