Skip to content

AxonOps — AI-Native Control Plane for Open Source Data Platforms

Kafka Client Failure Handling

Kafka clients must handle various failure scenarios including network errors, broker failures, and timeout conditions. This guide covers retry mechanisms, idempotent producers, transactional semantics, and error recovery strategies.

Retriable, non-retriable, and fatal Kafka client error categoriesRetriable, non-retriable, and fatal Kafka client error categoriesFailure TypesRetriable ErrorsNon-Retriable ErrorsFatal ErrorsNOT_LEADER_OR_FOLLOWERREQUEST_TIMED_OUTNETWORK_EXCEPTIONCOORDINATOR_LOAD_IN_PROGRESSTOPIC_AUTHORIZATION_FAILEDRECORD_TOO_LARGEINVALID_REQUIRED_ACKSUNKNOWN_TOPIC_OR_PARTITIONOUT_OF_ORDER_SEQUENCE_NUMBERINVALID_PRODUCER_EPOCHTRANSACTIONAL_ID_AUTHORIZATION_FAILED

Error CodeNameCauseClient Action
3UNKNOWN_TOPIC_OR_PARTITIONTopic missing or metadata staleRefresh metadata; retry only if topic is expected to exist
5LEADER_NOT_AVAILABLEElection in progressWait and retry
6NOT_LEADER_OR_FOLLOWERStale metadataRefresh metadata, retry
7REQUEST_TIMED_OUTBroker too slowRetry
15NETWORK_EXCEPTIONConnection failedReconnect, retry
Error CodeNameCauseClient Action
13RECORD_TOO_LARGEMessage > max.message.bytesReduce message size
17INVALID_REQUIRED_ACKSInvalid acks settingFix configuration
29TOPIC_AUTHORIZATION_FAILEDNo permissionCheck ACLs
74INVALID_TXN_STATETransaction state errorAbort transaction

Producer request retry decision flowProducer request retry decision flowSend requestSuccess?yesnoComplete with resultRetriable error?yesnoRetries exhausted?yesnoThrow exceptionIncrement retry countApply backoffRefresh metadata (if needed)Retry requestThrow exception
# Number of retries
retries=2147483647 # Max int (default, Kafka 2.1+)
# Backoff between retries
retry.backoff.ms=100 # 100ms (default)
# Maximum retry backoff (Kafka 2.6+)
retry.backoff.max.ms=1000 # 1 second
# Total time for retries
delivery.timeout.ms=120000 # 2 minutes (default)
Exponential Backoff with JitterExponential Backoff with JitterRetry AttemptsAttempt 1Backoff 100msAttempt 2Backoff 200msAttempt 3Backoff 400msAttempt 40100200300400500600700800900100011001200130014001500

Timeout relationship inside delivery.timeout.msTimeout relationship inside delivery.timeout.msdelivery.timeout.ms (total)linger.msrequest.timeout.msretry timedelivery.timeout.ms >= linger.ms + request.timeout.msRetry attempts occur within this window
# Total delivery time budget
delivery.timeout.ms=120000 # 2 minutes
# Time for batch accumulation
linger.ms=10
# Time for single request
request.timeout.ms=30000 # 30 seconds
# Retries happen within delivery.timeout.ms
retries=2147483647

Idempotent producers enable exactly-once semantics within a single partition, preventing duplicate messages from retries.

Idempotent producer deduplicating a retried produce requestProducerBrokerProducer(PID=1)Producer(PID=1)BrokerBrokerProduce(seq=0)AckProduce(seq=1)Network timeout(ack lost)Produce(seq=1) [retry]Broker detects duplicate:PID=1, seq=1 already seenReturns success (idempotent)Ack (no duplicate written)Produce(seq=2)Ack
# Enable idempotence
enable.idempotence=true
# Required settings (set automatically when idempotence enabled)
acks=all
retries=2147483647
max.in.flight.requests.per.connection=5 # or less
ComponentDescription
Producer ID (PID)Unique ID assigned by broker
EpochIncrements on producer restart
Sequence NumberPer-partition, monotonically increasing
Idempotent producer state of producer ID, epoch, and per-partition sequence numbersIdempotent producer state of producer ID, epoch, and per-partition sequence numbersIdempotent Producer StateSequence NumbersPID: 12345Epoch: 0P0: 42P1: 17P2: 103Broker tracks highest seq per PID/partitionRejects out-of-order or duplicate sequences

Transactions enable exactly-once semantics across multiple partitions and consumer groups.

Transactional producer flow from InitProducerId to commitProducerTransactionBrokerProducerProducerTransactionCoordinatorTransactionCoordinatorBrokerBrokerInitializeInitProducerId(transactional.id)PID, EpochTransactionBeginTransactionAddPartitionsToTxnProduce(P0, TXN)AckProduce(P1, TXN)AckCommitEndTxn(COMMIT)WriteTxnMarker(COMMIT)AckSuccess
# Required for transactions
transactional.id=my-transactional-producer
# Automatically enabled with transactional.id
enable.idempotence=true
acks=all
# Transaction timeout
transaction.timeout.ms=60000 # 1 minute
producer.initTransactions();
try {
producer.beginTransaction();
producer.send(new ProducerRecord<>("orders", key, value));
producer.send(new ProducerRecord<>("inventory", key, update));
// Send consumer offsets as part of transaction
producer.sendOffsetsToTransaction(offsets, consumerGroupId);
producer.commitTransaction();
} catch (ProducerFencedException | OutOfOrderSequenceException e) {
// Fatal - close producer
producer.close();
} catch (KafkaException e) {
// Abort and retry
producer.abortTransaction();
}
SettingConsumer Behavior
isolation.level=read_uncommittedSees all messages (including uncommitted)
isolation.level=read_committedOnly sees committed messages

Consumer offset commit with auto-commit and manual commitConsumerCoordinatorConsumerConsumerCoordinatorCoordinatorpoll()Records (offset 100-150)Process recordsalt[Auto-commit]enable.auto.commit=trueCommit(151)(background)[Manual commit]commitSync(151)On failure before commit:Records 100-150 reprocessed(at-least-once)
consumer.subscribe(topics, new ConsumerRebalanceListener() {
@Override
public void onPartitionsRevoked(Collection<TopicPartition> partitions) {
// Commit pending offsets before losing partitions
try {
consumer.commitSync(currentOffsets);
} catch (CommitFailedException e) {
log.warn("Commit failed on revoke", e);
}
}
@Override
public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
// Initialize state for new partitions
for (TopicPartition partition : partitions) {
initializePartitionState(partition);
}
}
});
ScenarioImpactMitigation
Consumer crashUncommitted messages reprocessedIdempotent processing
Long processingRebalance triggeredIncrease max.poll.interval.ms
Commit failureDuplicate processingRetry commit
Deserialization errorPoison pillDead letter queue

Client connection state transitions during reconnectionClient connection state transitions during reconnectionConnectedDisconnectedReconnectingExponential backoff:reconnect.backoff.msup to reconnect.backoff.max.msinitial connectconnection lostbackoff expiredfailuresuccess
# Initial reconnection backoff
reconnect.backoff.ms=50
# Maximum reconnection backoff
reconnect.backoff.max.ms=1000
# Connection timeout
socket.connection.setup.timeout.ms=10000
socket.connection.setup.timeout.max.ms=30000

producer.send(record, (metadata, exception) -> {
if (exception == null) {
// Success
log.info("Sent to partition {} offset {}",
metadata.partition(), metadata.offset());
} else if (exception instanceof RetriableException) {
// Retriable - will be retried automatically
log.warn("Retriable error, will retry: {}", exception.getMessage());
} else {
// Non-retriable - handle or dead-letter
log.error("Non-retriable error: {}", exception.getMessage());
sendToDeadLetter(record, exception);
}
});
while (running) {
try {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
try {
processRecord(record);
} catch (ProcessingException e) {
// Send to dead letter queue
sendToDeadLetter(record, e);
}
}
consumer.commitSync();
} catch (WakeupException e) {
// Shutdown signal
if (running) throw e;
} catch (CommitFailedException e) {
// Rebalance during commit - records will be redelivered
log.warn("Commit failed due to rebalance", e);
}
}
Dead letter queue topology for failed record processingDead letter queue topology for failed record processingMain ConsumerDLQ Consumerpoll()process()Retry/AlertMain TopicDead Letter Topicon failure

ConfigurationGuaranteeDuplicatesLoss
acks=0Fire and forgetPossiblePossible
acks=1Leader ackPossiblePossible
acks=allFull ISR ackPossibleNo*
acks=all + idempotenceExactly-once (partition)NoNo
TransactionsExactly-once (cross-partition)NoNo

*With min.insync.replicas configured

PatternGuaranteeDuplicatesLoss
Auto-commitNot guaranteed (can be at-most-once)PossiblePossible
Commit before processAt-most-onceNoPossible
Commit after processAt-least-oncePossibleNo
TransactionalExactly-onceNoNo

MetricDescriptionAlert Condition
record-error-rateErrors per second> 0 sustained
record-retry-rateRetries per secondUnusually high
record-send-rateSuccessful sendsDrop from baseline
request-latency-avgRequest latency> threshold
MetricDescriptionAlert Condition
failed-rebalance-rateFailed rebalances> 0
last-poll-seconds-agoTime since poll> max.poll.interval.ms
commit-latency-avgCommit latency> threshold
records-lagConsumer lagIncreasing

PracticeRationale
Enable idempotencePrevent duplicates from retries
Set reasonable delivery.timeout.msBound total retry time
Use callbacks for error handlingNon-blocking error handling
Implement dead letter queueHandle persistent failures
PracticeRationale
Commit after processingAt-least-once guarantee
Implement idempotent processingHandle duplicates
Use read_committed with transactionsSee only committed data
Handle rebalance callbacksClean state management

FeatureMinimum Version
Basic retries0.8.0
Idempotent producer0.11.0
Transactions0.11.0
delivery.timeout.ms2.1.0
Cooperative rebalancing2.4.0