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
- Remove the `transaction.timeout.ms` setting from your producer config — under 2PC the external coordinator owns finalization timing.
- If you genuinely need a timeout, leave 2PC disabled and use the standard transactional producer.
- 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
- Two-phase commit moves timeout ownership to an external coordinator — never pair it with broker-side transaction.timeout.ms.
- When toggling 2PC on, audit config files/env vars for leftover transaction.timeout.ms entries.
- Keep 2PC and standard transactional producers as separate, named config profiles.
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
- Invalid producer ID and epoch values: {producerId}:{epoch}.
- Invalid serialized transaction state format: {serializedStat
- Must set retries to non-zero when using the idempotent produ
- Cannot set a transactional.id without also enabling idempote
- Must set acks to all in order to use the idempotent producer
AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03).
Data as JSON: /data/errors/c754c4ec4d6ba557.json.
Report an issue: GitHub.