Home/Learn/RabbitMQ/Dead Letter Exchange (DLX)

Dead Letter Exchange (DLX)

Intermediate
Dead-Lettering

Rejected, nacked, or expired messages are routed to a DLX; configure x-dead-letter-exchange on the source queue. DLQs are essential for failure inspection and retry workflows.

Overview

A Dead Letter Exchange (DLX) is a regular exchange that receives messages which cannot be processed from a source queue. A message "dead-letters" for three reasons: it was rejected with requeue=false (basicNack/basicReject), its TTL expired, or the queue hit its x-max-length limit and overflow policy is reject-publish. You configure a DLX on the source queue via the x-dead-letter-exchange argument. Dead-lettered messages are routed to the DLX and from there to a Dead Letter Queue (DLQ) for inspection, retry, or alerting. The DLX pattern is one of the most important RabbitMQ patterns for production systems — it prevents message loss and provides an audit trail of failures.

Configuring a DLX on a Queue

Declare the DLX (a regular direct or topic exchange), a DLQ, and bind them. Then declare the source queue with the x-dead-letter-exchange argument pointing to the DLX. Optionally, set x-dead-letter-routing-key to override the routing key used when dead-lettering (by default, the original routing key is preserved).

Java — DLX Setup (AMQP Client)
Channel channel = connection.createChannel();

// 1. Declare the DLX (a regular direct exchange)
channel.exchangeDeclare("orders.dlx", BuiltinExchangeType.DIRECT, true);

// 2. Declare the DLQ
channel.queueDeclare("orders.dlq", true, false, false, null);

// 3. Bind DLQ to DLX
channel.queueBind("orders.dlq", "orders.dlx", "order-queue");

// 4. Declare the source queue with DLX argument
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange",    "orders.dlx");
args.put("x-dead-letter-routing-key", "order-queue"); // optional override
args.put("x-message-ttl",             60_000);        // 60 s TTL — expired msgs → DLX

channel.queueDeclare("order-queue", true, false, false, args);

// Now: nack with requeue=false → message goes to orders.dlq
// Or: message not consumed within 60s → goes to orders.dlq

Spring AMQP — DLX with @Bean Configuration

In Spring AMQP, declare the DLX, DLQ, source queue, and bindings as beans. QueueBuilder.withArgument() sets the x-dead-letter-exchange on the source queue.

Java — Spring AMQP DLX Configuration
@Configuration
public class RabbitConfig {

    // DLX — regular direct exchange
    @Bean
    public DirectExchange ordersDlx() {
        return new DirectExchange("orders.dlx", true, false);
    }

    // DLQ — messages land here
    @Bean
    public Queue ordersDlq() {
        return QueueBuilder.durable("orders.dlq").build();
    }

    @Bean
    public Binding dlqBinding(Queue ordersDlq, DirectExchange ordersDlx) {
        return BindingBuilder.bind(ordersDlq).to(ordersDlx).with("order-queue");
    }

    // Source queue — configured with DLX
    @Bean
    public Queue orderQueue() {
        return QueueBuilder.durable("order-queue")
            .withArgument("x-dead-letter-exchange",    "orders.dlx")
            .withArgument("x-dead-letter-routing-key", "order-queue")
            .withArgument("x-message-ttl",             60_000)
            .build();
    }
}

// Consumer — nack to dead-letter on permanent failure
@RabbitListener(queues = "order-queue")
public void onOrder(Order order, Channel channel,
        @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
    try {
        orderService.process(order);
        channel.basicAck(tag, false);
    } catch (UnrecoverableException e) {
        channel.basicNack(tag, false, false);  // → DLQ
    }
}

Retry via Delayed DLQ

A common pattern is retry-with-backoff using a DLX: nack a failed message into a "retry queue" with a TTL; after the TTL expires it dead-letters back to the original exchange. Each retry increments an x-death header count. After max retries, route to a permanent DLQ for manual inspection.

Java — Retry via Delayed DLQ
// Retry topology:
// order-queue → (nack) → retry.orders (TTL=30s) → (expire) → order-queue
// order-queue → (max retries exceeded) → orders.dlq.permanent

@Bean
public Queue retryQueue() {
    return QueueBuilder.durable("retry.orders")
        .withArgument("x-message-ttl",             30_000)  // 30s before retry
        .withArgument("x-dead-letter-exchange",    "")       // default exchange
        .withArgument("x-dead-letter-routing-key", "order-queue") // back to source
        .build();
}

// In consumer: check x-death header retry count
@RabbitListener(queues = "order-queue")
public void onOrder(Order order, Channel channel,
        @Header(AmqpHeaders.DELIVERY_TAG) long tag,
        @Header(value = "x-death", required = false) List<Map<String, Object>> xDeath)
        throws IOException {

    long retryCount = xDeath == null ? 0 :
        xDeath.stream().mapToLong(d -> (Long) d.get("count")).sum();

    if (retryCount >= 3) {
        channel.basicNack(tag, false, false);  // → permanent DLQ after 3 retries
        return;
    }

    try {
        orderService.process(order);
        channel.basicAck(tag, false);
    } catch (Exception e) {
        rabbitTemplate.convertAndSend("", "retry.orders", order); // → 30s retry
        channel.basicAck(tag, false);  // ack original, retry is a new message
    }
}

Key Points to Remember

  • 1A DLX is a regular exchange; configure it on the source queue via x-dead-letter-exchange argument.
  • 2Messages dead-letter for three reasons: nacked with requeue=false, TTL expired, or queue overflow (reject-publish policy).
  • 3Always monitor DLQ depth — a growing DLQ indicates a broken consumer or a schema/data problem.
  • 4The x-death header tracks how many times a message has been dead-lettered and the reason — use it for retry-count logic.
  • 5Retry-with-backoff pattern: nack → retry queue with TTL → expires back to source queue → after N retries → permanent DLQ.
  • 6In Spring AMQP, use QueueBuilder.withArgument("x-dead-letter-exchange", "dlx-name") to configure the DLX on the source queue.

Interview Questions

Sign in to ask Aria
1

What is a Dead Letter Exchange and when does a message get dead-lettered?

EasyAmazon
2

How would you configure a DLX in Spring AMQP?

MediumFlipkart
3

How would you implement a retry-with-backoff mechanism using RabbitMQ DLX?

HardUber
4

What is the x-death header and how can you use it to limit retries?

MediumNetflix
5

A message TTL expires but there is no DLX configured. What happens to the message?

MediumGoogle

Ask Aria about Dead Letter Exchange (DLX)

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…