What is dead-lettering in RabbitMQ/Spring AMQP, and how do you configure a dead-letter exchange on a queue?
answer
- x-dead-letter-exchange queue arg
- reject/nack requeue=false, TTL, max-length
- DLX -> DLQ for inspection
- x-death header count/reason
- arguments set at declare time, immutable
basics
~10 sDead-lettering routes messages that can't be processed to a separate exchange instead of losing them. You set the queue arguments x-dead-letter-exchange (and optionally x-dead-letter-routing-key) when declaring the queue.
solid answer
~30 sDead-lettering is RabbitMQ's mechanism for shunting undeliverable messages to a designated 'dead-letter exchange' (DLX) so they aren't silently dropped. You attach it by declaring the main queue with the arguments x-dead-letter-exchange and optionally x-dead-letter-routing-key. A message is dead-lettered when: it's rejected/nacked with requeue=false, its per-message or per-queue TTL expires, or the queue exceeds a length limit. From the DLX it's routed to a dead-letter queue (DLQ) for inspection or reprocessing. In Spring AMQP you set these via QueueBuilder.withArgument(...) or Queue arguments in the bean definition. RabbitMQ adds an x-death header recording why and how often a message was dead-lettered.
code
java · 28 lines@Configuration
class DlxConfig {
@Bean
Queue ordersQueue() {
return QueueBuilder.durable("orders")
.withArgument("x-dead-letter-exchange", "orders.dlx")
.withArgument("x-dead-letter-routing-key", "orders.dead")
.build();
}
@Bean
DirectExchange ordersDlx() {
return new DirectExchange("orders.dlx");
}
@Bean
Queue ordersDlq() {
return QueueBuilder.durable("orders.dlq").build();
}
@Bean
Binding dlqBinding() {
return BindingBuilder.bind(ordersDlq())
.to(ordersDlx())
.with("orders.dead");
}
}go deeper
Know that dead-lettering saves failed messages to a separate queue and it's configured via the x-dead-letter-exchange queue argument.
Know the three trigger conditions (reject-no-requeue, TTL, max-length) and that arguments are immutable at declare time.
Explain the x-death header, DLX vs DLQ binding, and how the listener's exception maps to the reject that triggers dead-lettering.
Design the topology (naming, bindings, alerting) and use x-death for delayed-retry loops and poison-message policy.
**Dead-lettering** is RabbitMQ's built-in safety net for messages that cannot be processed. Instead of discarding them, the broker re-routes them to a **dead-letter exchange (DLX)** — an ordinary exchange you nominate — which typically fans them into a **dead-letter queue (DLQ)** where you can inspect, alert on, or replay them. **How you configure it:** A DLX is not a special server-wide setting; it's a *queue argument* set when the queue is **declared** (arguments are immutable afterward — you must delete and recreate the queue to change them): - `x-dead-letter-exchange` — the name of the exchange dead-lettered messages are published to. - `x-dead-letter-routing-key` — optional; overrides the routing key used when republishing to the DLX. If omitted, the message's *original* routing key is reused. **When does a message get dead-lettered?** Three triggers: 1. The consumer **rejects** it with `basic.reject` or `basic.nack` and `requeue=false`. 2. The message's **TTL expires** (per-message `expiration` or the queue's `x-message-ttl`). 3. The queue hits a **length limit** (`x-max-length` / `x-max-length-bytes`) and the message is dropped from the head. **The x-death header:** When RabbitMQ dead-letters a message it stamps (or updates) an `x-death` header — an array of entries per queue/reason recording the `count`, `reason` (rejected / expired / maxlen), `queue`, `time`, original `exchange`, and `routing-keys`. This is how you can build retry-count logic or delayed-retry loops. **Spring AMQP specifics:** You declare the arguments on the `Queue` bean. Modern idiom uses `QueueBuilder`: ```java QueueBuilder.durable("orders") .withArgument("x-dead-letter-exchange", "orders.dlx") .withArgument("x-dead-letter-routing-key", "orders.dead") .build(); ``` Spring's `RabbitAdmin` declares these at startup. Importantly, the *application* usually decides *when* a message is unrecoverable — by having the listener throw an exception that the container translates into a reject-without-requeue (e.g. `AmqpRejectAndDontRequeueException`), which then triggers the broker's dead-lettering. **Gotcha:** Declaring `x-dead-letter-exchange` does nothing by itself unless something actually rejects-without-requeue, sets a TTL, or a length limit is hit. Many candidates think adding the argument alone routes failures — it must be paired with the right rejection behavior. Also, the DLX and DLQ must exist and be bound, or dead-lettered messages are silently dropped (unroutable).
- You added x-dead-letter-exchange but messages never appear in the DLQ. Name two likely causes.Either nothing is actually rejecting with requeue=false (the listener requeues instead, e.g. defaultRequeueRejected=true and no AmqpRejectAndDontRequeueException), or the DLX has no binding to the DLQ so republished messages are unroutable and dropped.
- Can you change a queue's dead-letter exchange after it's created?No — queue arguments are fixed at declaration. You must delete and redeclare the queue (or use a new queue), because RabbitMQ rejects a redeclare with different arguments as a channel-level PRECONDITION_FAILED error.