Dead letter exchanges, TTL and retry with backoff

RabbitMQ Course · lesson 8 of 15 · 6 min read

A dead letter exchange plus a TTL queue is how you build delayed retry without any plugin.

Open this lesson in the learning hub

Key points

  • Set x-dead-letter-exchange on a queue and rejected messages are republished there.
  • Exactly four reasons dead letter a message: rejected, expired, maxlen and delivery_limit.
  • The x-death header records the reason, the origin queue, an attempt count and a timestamp.
  • Queue-wide x-message-ttl and the per-message expiration property are both in milliseconds.
  • A per-message TTL only fires at the head of the queue, so one slow message delays the ones behind it.
  • Backoff is just a wait queue with no consumer: the TTL expires and its DLX returns the message.

Example

// work queue -> on reject, goes to the retry exchange
Map<String, Object> work = Map.of(
    "x-dead-letter-exchange", "orders.retry");
ch.queueDeclare("invoicing", true, false, false, work);

// retry queue has NO consumer - the TTL is the delay
Map<String, Object> retry = Map.of(
    "x-message-ttl", 30_000,                       // wait 30s
    "x-dead-letter-exchange", "orders",            // then back to work
    "x-dead-letter-routing-key", "order.created");
ch.queueDeclare("invoicing.retry.30s", true, false, false, retry);

ch.queueBind("invoicing.retry.30s", "orders.retry", "order.created");

// chain 5s / 30s / 5m queues for exponential backoff, and give the
// last one a DLX pointing at a parking lot queue nobody drains

A retry queue is a queue with no consumer: the TTL is the delay and the DLX is the return path.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the RabbitMQ Course course, and every lesson in it is listed on the RabbitMQ Course contents page.