apache/kafka · error · IllegalStateException
Transactional method invoked on a non-transactional producer
Error message
Transactional method invoked on a non-transactional producer.
What it means
Thrown by TransactionManager.ensureTransactional() when a transactional method (beginTransaction, commitTransaction, abortTransaction, sendOffsetsToTransaction, etc.) is called on a producer that has no transactional.id configured. The producer is in idempotent-only or plain mode; transactional semantics require a transactional.id, so the client refuses the call rather than silently behaving non-transactionally.
Source
Thrown at clients/src/main/java/org/apache/kafka/clients/producer/internals/TransactionManager.java:1177
} else if (target == State.FATAL_ERROR || target == State.ABORTABLE_ERROR) {
if (error == null)
throw new IllegalArgumentException("Cannot transition to " + target + " with a null exception");
lastError = error;
} else {
lastError = null;
}
if (lastError != null)
log.debug("Transition from state {} to error state {}", currentState, target, lastError);
else
log.debug("Transition from state {} to {}", currentState, target);
currentState = target;
}
private void ensureTransactional() {
if (!isTransactional())
throw new IllegalStateException("Transactional method invoked on a non-transactional producer.");
}
private void maybeFailWithError() {
if (!hasError()) {
return;
}
// for ProducerFencedException, do not wrap it as a KafkaException
// but create a new instance without the call trace since it was not thrown because of the current call
if (lastError instanceof ProducerFencedException) {
throw new ProducerFencedException("Producer with transactionalId '" + transactionalId
+ "' and " + producerIdAndEpoch + " has been fenced by another producer " +
"with the same transactionalId");
}
if (lastError instanceof InvalidProducerEpochException) {
throw new InvalidProducerEpochException("Producer with transactionalId '" + transactionalId
+ "' and " + producerIdAndEpoch + " attempted to produce with an old epoch");
}
if (lastError instanceof IllegalStateException) {View on GitHub (pinned to c31c9215e1)
Solutions
- Set transactional.id in producer config to a stable, unique-per-producer-instance value (required for EOS).
- If transactions are not actually needed, stop calling transactional methods — use the producer in plain/idempotent mode.
- Ensure enable.idempotence=true (it is implied for transactional producers) and that transactional.id is read from the same config source.
- Double-check the property name spelling against ProducerConfig.TRANSACTIONAL_ID_CONFIG.
Example fix
// before
props.put("enable.idempotence", "true");
KafkaProducer<String,String> p = new KafkaProducer<>(props);
p.beginTransaction(); // throws
// after
props.put("transactional.id", "order-service-1");
props.put("enable.idempotence", "true");
KafkaProducer<String,String> p = new KafkaProducer<>(props);
p.initTransactions();
p.beginTransaction(); Defensive patterns
Strategy: validation
Validate before calling
String txnId = configs.get("transactional.id") == null ? null : String.valueOf(configs.get("transactional.id"));
if (txnId == null || txnId.isEmpty()) {
throw new IllegalArgumentException("transactional.id must be set to use transactional methods");
} Type guard
// Narrow a producer reference to a transactional one before calling txn methods.
boolean isTransactional(java.util.Map<String,Object> configs) {
Object v = configs.get("transactional.id");
return v != null && !String.valueOf(v).trim().isEmpty();
} Try / catch
try {
producer.initTransactions();
} catch (IllegalStateException ise) {
// producer was built without transactional.id; rebuild config with it
} Prevention
- Set transactional.id in producer config whenever you intend to use any transactional method.
- Keep separate configurations (and ideally separate producer instances) for transactional vs non-transactional use.
- Add a startup assert that transactional.id is present before invoking any transactional API.
When it happens
Trigger: Constructing KafkaProducer without setting transactional.id, then calling any transactional API. ensureTransactional() (line 1175-1178) checks isTransactional() — which is true only when transactional.id != null — and throws.
Common situations: Adopting exactly-once patterns but forgetting the transactional.id config property; property typo (e.g. "transaction.id" instead of "transactional.id"); using a shared non-transactional producer bean and accidentally calling commitTransaction(); config loaded from a file that omitted transactional.id.
Related errors
- Cannot send offsets if a transaction is not in progress (cur
- Cannot add partition {topicPartition} to transaction before
- Cannot add partition {topicPartition} to transaction while i
- Producer with transactionalId '{transactionalId}' and {produ
- Cannot set a transactional.id without also enabling idempote
AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03).
Data as JSON: /data/errors/bcd61ca3395722fc.json.
Report an issue: GitHub.