apache/kafka · error · IllegalStateException
Cannot attempt operation `{operation}` because the previous
Error message
Cannot attempt operation `{operation}` because the previous call to `{previousOperation}` timed out and must be retried What it means
Thrown by TransactionManager.throwIfPendingState when a transactional operation is attempted while pendingTransition is still in flight and not yet acked, meaning a previous transactional call returned its future but the corresponding RPC did not complete within the wait window (timed out). The state machine refuses to start a new transition because the prior one's outcome is unknown — the broker may still be processing it. The user must retry the SAME previous operation so the producer can reconcile its state, rather than issuing a different one.
Source
Thrown at clients/src/main/java/org/apache/kafka/clients/producer/internals/TransactionManager.java:1312
.setGenerationIdOrMemberEpoch(groupMetadata.generationId())
.setGroupInstanceId(groupMetadata.groupInstanceId().orElse(null))
.setTopics(topics);
var builder = allHaveTopicIds
? TxnOffsetCommitRequest.Builder.forTopicIdsOrNames(data, isTransactionV2Enabled())
: TxnOffsetCommitRequest.Builder.forTopicNames(data, isTransactionV2Enabled());
if (result == null) {
// In this case, transaction V2 is in use.
return new TxnOffsetCommitHandler(builder, topicNamesByIds);
}
return new TxnOffsetCommitHandler(result, builder, topicNamesByIds);
}
private void throwIfPendingState(TransactionOperation operation) {
if (pendingTransition != null) {
if (pendingTransition.result.isAcked()) {
pendingTransition = null;
} else {
throw new IllegalStateException("Cannot attempt operation `" + operation + "` "
+ "because the previous call to `" + pendingTransition.operation + "` "
+ "timed out and must be retried");
}
}
}
private TransactionalRequestResult handleCachedTransactionRequestResult(
Supplier<TransactionalRequestResult> transactionalRequestResultSupplier,
State nextState,
String operation
) {
ensureTransactional();
if (pendingTransition != null) {
if (pendingTransition.result.isAcked()) {
pendingTransition = null;
} else if (nextState != pendingTransition.state) {
throw new IllegalStateException("Cannot attempt operation `" + operation + "` "View on GitHub (pinned to c31c9215e1)
Solutions
- Retry the SAME operation that timed out (the one reported as previousOperation) so the producer can resolve the pending transition — do not start a different transactional operation.
- Increase delivery.timeout.ms and transaction.timeout.ms (and matching transaction.max.timeout.ms on the broker) to comfortably exceed expected RPC latency under peak load.
- Investigate broker-side latency: check coordinator load, network, GC, and under-replicated __transaction_state partitions.
- If the previous operation is unrecoverable, close() the producer and create a new one with initTransactions() to reset the state machine.
Example fix
// before
try {
producer.commitTransaction();
} catch (TimeoutException e) {
producer.beginTransaction(); // throws 293: prior commit still pending
}
// after
boolean committed = false;
try {
producer.commitTransaction();
committed = true;
} catch (TimeoutException e) {
// retry the same pending operation
producer.commitTransaction();
committed = true;
} Defensive patterns
Strategy: retry
Validate before calling
// The previous operation timed out and left a pending transition that must be retried. // There is no public pre-check; configure a larger delivery.timeout.ms so the prior call // does not time out, and structure code so the timed-out call is idempotently retried: props.put(ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG, 300000); // 5 min >= request + block timeouts props.put(ProducerConfig.TRANSACTION_TIMEOUT_CONFIG, 60000); // align with broker
Try / catch
try {
producer.commitTransaction();
} catch (IllegalStateException e) {
if (e.getMessage() != null && e.getMessage().contains("timed out and must be retried")) {
// The prior beginTransaction()/commitTransaction() timed out but may still complete.
// Retry the SAME operation; it is idempotent in transaction V2 with the same transition.
producer.commitTransaction(); // single retry; if it fails again, close producer.
} else {
throw e;
}
} Prevention
- Treat commitTransaction()/abortTransaction()/beginTransaction() as retryable on timeout; do not give up after the first timeout.
- Ensure delivery.timeout.ms >= request.timeout.ms + some retry slack so the producer does not pre-emptively time out internally.
- Log which operation was pending when you retry; never switch to a different operation (e.g. abort after commit timed out) on the same producer — that triggers the other (294) variant.
When it happens
Trigger: Called from the Sender thread path before any transactional operation (begin/commit/abort/sendOffsetsToTransaction) when pendingTransition != null and pendingTransition.result.isAcked() is false. The previous call (e.g. commitTransaction) timed out — its TransactionalRequestResult.await threw TimeoutException and was caught, but the producer was reused for another operation instead of retrying the timed-out one.
Common situations: transaction.timeout.ms or delivery.timeout.ms too low relative to broker response time under load; network latency or GC pauses causing the InitProducerId/EndTxn/AddPartitionsToTxn RPC to exceed the client wait; broker rebalance or controller failover mid-transaction; user code catching the timeout and trying beginTransaction again instead of retrying the in-flight commit/abort.
Related errors
- Timeout expired after {timeoutMs}ms while awaiting {operatio
- Cannot set a transactional.id without also enabling idempote
- 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
AI-assisted analysis of apache/kafka@c31c9215e1 (2026-08-03).
Data as JSON: /data/errors/ec5a51f8fe12fe8c.json.
Report an issue: GitHub.