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

  1. Set transactional.id in producer config to a stable, unique-per-producer-instance value (required for EOS).
  2. If transactions are not actually needed, stop calling transactional methods — use the producer in plain/idempotent mode.
  3. Ensure enable.idempotence=true (it is implied for transactional producers) and that transactional.id is read from the same config source.
  4. 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

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


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