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

  1. 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.
  2. 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.
  3. Investigate broker-side latency: check coordinator load, network, GC, and under-replicated __transaction_state partitions.
  4. 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

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


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