Dead Letter Exchange (DLX)
IntermediateRejected, 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).
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.dlqSpring 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.
@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.
// 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 AriaWhat is a Dead Letter Exchange and when does a message get dead-lettered?
How would you configure a DLX in Spring AMQP?
How would you implement a retry-with-backoff mechanism using RabbitMQ DLX?
What is the x-death header and how can you use it to limit retries?
A message TTL expires but there is no DLX configured. What happens to the message?
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.