Kafka
Spring-Kafka
Dead Letter Queue
Message Brokers
Distributed Systems

Dead letter queue (DLQ) for Kafka with spring-kafka

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Kafka does not have a built-in dead letter queue object, but it is common to implement the same pattern with a dedicated Kafka topic. In Spring Kafka, the standard approach is to use an error handler plus a DeadLetterPublishingRecoverer so failed records are retried and then published to a dead-letter topic instead of blocking the consumer forever.

What a DLQ Solves

When a consumer cannot process a record because of bad data, a deserialization problem, or a downstream business failure, you usually want one of two outcomes:

  • retry the message a few times
  • if it still fails, move it aside for inspection

A DLQ topic provides that second destination. It keeps the main consumer flowing while preserving the failed record for analysis or reprocessing.

The Core Spring Kafka Pieces

In modern Spring Kafka, the usual components are:

  • 'DefaultErrorHandler'
  • 'DeadLetterPublishingRecoverer'
  • 'KafkaTemplate'

DefaultErrorHandler handles retries. DeadLetterPublishingRecoverer decides how to publish the failed record once retries are exhausted.

A Typical Configuration

java
1import org.springframework.context.annotation.Bean;
2import org.springframework.context.annotation.Configuration;
3import org.springframework.kafka.core.KafkaTemplate;
4import org.springframework.kafka.listener.DefaultErrorHandler;
5import org.springframework.kafka.listener.DeadLetterPublishingRecoverer;
6import org.springframework.util.backoff.FixedBackOff;
7
8@Configuration
9public class KafkaErrorHandlingConfig {
10
11    @Bean
12    public DefaultErrorHandler errorHandler(KafkaTemplate<Object, Object> kafkaTemplate) {
13        DeadLetterPublishingRecoverer recoverer =
14            new DeadLetterPublishingRecoverer(kafkaTemplate);
15
16        FixedBackOff backOff = new FixedBackOff(1000L, 2L);
17
18        return new DefaultErrorHandler(recoverer, backOff);
19    }
20}

This means:

  • try processing
  • retry twice with a one-second delay
  • if it still fails, publish to the dead-letter topic

By default, the dead-letter topic name is often based on the original topic plus a suffix such as .DLT.

Listener Example

java
1import org.springframework.kafka.annotation.KafkaListener;
2import org.springframework.stereotype.Component;
3
4@Component
5public class OrderListener {
6
7    @KafkaListener(topics = "orders", groupId = "orders-group")
8    public void listen(String payload) {
9        if (payload.contains("bad")) {
10            throw new IllegalArgumentException("Invalid payload");
11        }
12
13        System.out.println("Processed: " + payload);
14    }
15}

If a record keeps failing, the error handler pushes it to the dead-letter topic instead of letting it poison the consumer forever.

Customizing the Dead-Letter Topic

You can route failures to a custom topic instead of the default naming convention:

java
1DeadLetterPublishingRecoverer recoverer =
2    new DeadLetterPublishingRecoverer(
3        kafkaTemplate,
4        (record, ex) -> new org.apache.kafka.common.TopicPartition("orders-dlq", record.partition())
5    );

That is useful when you want a stable operational topic name or different routing rules based on exception type.

Why a DLQ Is Better Than Infinite Retry

Infinite retries can stall a consumer partition on one bad record. In some systems that is exactly what you want, but in many event-processing pipelines it is better to capture the failure and move on.

A DLQ also helps operations teams answer questions like:

  • which records are failing repeatedly
  • which exception types are most common
  • do we need replay or data repair tools

Without a DLQ, those failures are harder to inspect systematically.

Handling DLT Records

A DLQ is not the end of the design. You should decide what happens next:

  • manual inspection
  • an admin replay tool
  • an automated repair consumer
  • alerting and dashboards

The dead-letter topic is valuable only if the team knows how to observe and act on it.

Common Pitfalls

One common mistake is following older examples that use deprecated handler classes and assuming they are still the current recommendation. In newer Spring Kafka code, DefaultErrorHandler is the standard place to start.

Another issue is sending everything to the DLQ immediately with no retry policy, even for transient failures such as brief network issues. Retries and DLQs should work together.

A third pitfall is creating a DLQ topic but never monitoring it. That simply moves failures to a quieter place instead of solving them operationally.

Summary

  • In Spring Kafka, a DLQ is usually implemented as a separate Kafka topic.
  • Use DefaultErrorHandler with DeadLetterPublishingRecoverer for the standard pattern.
  • Retries happen first; failed records can then be published to a dead-letter topic.
  • Customize DLT routing when you need stable names or exception-based behavior.
  • A DLQ is useful only if the team also monitors and handles the failed records.

Course illustration
Course illustration

All Rights Reserved.