Skip to content

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

Kafka Producer Error Handling

Proper error handling ensures reliable message delivery. This document covers error types, timeout configuration, and recovery patterns.


Producer error categories: retriable, non-retriable, and fatalProducer error categories: retriable, non-retriable, and fatalProducer ErrorsRetriableNon-RetriableFatalNetworkExceptionNotEnoughReplicasExceptionTimeoutExceptionSerializationExceptionRecordTooLargeExceptionInvalidTopicExceptionProducerFencedExceptionOutOfOrderSequenceExceptionAuthorizationException
TypeProducer BehaviorApplication Action
RetriableAutomatic retryWait or handle timeout
Non-RetriableFail immediatelyFix data or config
FatalCannot recoverClose producer

# Total time to deliver message
delivery.timeout.ms=120000
# Per-request timeout
request.timeout.ms=30000
# Retry backoff
retry.backoff.ms=100
retry.backoff.max.ms=1000
Producer retry timeline within delivery.timeout.msProducer retry timeline within delivery.timeout.msTimelineSendRetry 1Retry 2Retry 3delivery.timeout.ms0100300700120000

producer.send(record, (metadata, exception) -> {
if (exception == null) {
log.info("Sent to {} @ {}", metadata.partition(), metadata.offset());
return;
}
if (exception instanceof RetriableException) {
// Already retried and failed
log.error("Retriable error exhausted", exception);
deadLetterQueue.send(record);
} else if (exception instanceof SerializationException) {
// Bad data
log.error("Serialization failed", exception);
metrics.increment("serialization_errors");
} else {
// Other error
log.error("Send failed", exception);
alertService.notify(exception);
}
});
int maxRetries = 3;
for (int attempt = 0; attempt < maxRetries; attempt++) {
try {
RecordMetadata metadata = producer.send(record).get();
log.info("Sent to {} @ {}", metadata.partition(), metadata.offset());
break;
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RetriableException && attempt < maxRetries - 1) {
log.warn("Retry {} of {}", attempt + 1, maxRetries);
Thread.sleep(100 * (attempt + 1));
} else {
throw e;
}
}
}

ErrorCauseSolution
TimeoutExceptionBroker slow or unreachableCheck broker health, increase timeout
NotEnoughReplicasExceptionISR too smallWait for replicas to catch up
RecordTooLargeExceptionMessage > max.request.sizeIncrease limit or reduce message size
SerializationExceptionSerializer failureFix serialization logic
ProducerFencedExceptionAnother producer with same txn.idEnsure single instance

public void sendWithDLQ(ProducerRecord<String, String> record) {
producer.send(record, (metadata, exception) -> {
if (exception != null) {
// Send to DLQ
ProducerRecord<String, String> dlqRecord = new ProducerRecord<>(
"dead-letter-queue",
record.key(),
record.value()
);
dlqRecord.headers()
.add("original-topic", record.topic().getBytes())
.add("error", exception.getMessage().getBytes());
dlqProducer.send(dlqRecord);
}
});
}

PracticeRationale
Always handle callbacksDetect delivery failures
Use appropriate timeoutsBalance reliability and latency
Log errors with contextEnable debugging
Implement DLQ for critical dataPrevent data loss
Monitor error ratesDetect systemic issues