Home/Learn/RabbitMQ/Message TTL

Message TTL

Intermediate
Dead-Lettering

Set x-message-ttl (queue-level) or the expiration property (per-message) to expire messages not consumed within the specified milliseconds; expired messages are dead-lettered.

Overview

Message TTL (Time-To-Live) controls how long a message can sit in a queue before it is discarded (or dead-lettered). There are two scopes: **queue-level TTL** (`x-message-ttl` argument on queue declaration) — all messages in the queue share the same expiry; and **per-message TTL** (`expiration` AMQP property) — each message can have its own expiry, set by the producer. Both are in milliseconds. When a message expires, it is either dropped silently or routed to the configured DLX. TTL + DLX is the building block for **retry-with-delay** patterns: dead-letter an expired message from a holding queue back to the original processing queue after a fixed delay.

Queue-Level TTL with x-message-ttl

Set `x-message-ttl` on the queue declaration. Every message in this queue expires after the specified milliseconds if not consumed. Expired messages are dead-lettered to the DLX or dropped if no DLX is configured. Queue-level TTL only expires messages at the head of the queue — messages are not checked mid-queue.

Spring AMQP — queue-level x-message-ttl
@Bean
Queue ordersQueue() {
    return QueueBuilder.durable("orders.processing")
        .withArgument("x-message-ttl", 60_000)             // 60 seconds
        .withArgument("x-dead-letter-exchange", "orders.dlx")
        .build();
}

// Or via RabbitAdmin (runtime)
rabbitAdmin.declareQueue(new Queue("orders.processing", true, false, false,
    Map.of("x-message-ttl", 60_000,
           "x-dead-letter-exchange", "orders.dlx")));

// Important: queue-level TTL checks messages at the QUEUE HEAD
// Messages buried mid-queue are NOT expired until they reach the head
// (Per-message TTL can expire messages anywhere in the queue — checked on delivery)

Per-Message TTL via expiration Property

`expiration` is set by the producer as an AMQP message property (as a string of milliseconds). Per-message TTL is checked when the message is about to be delivered — a message with a shorter TTL that is buried behind longer-TTL messages may not expire until it reaches the front of the queue. For reliable per-message expiry, consider queue-level TTL instead.

Spring AMQP — per-message expiration property
// Per-message TTL in Java AMQP client
AMQP.BasicProperties props = new AMQP.BasicProperties.Builder()
    .expiration("30000")   // 30 seconds, as string
    .deliveryMode(2)       // persistent
    .build();
channel.basicPublish("orders.exchange", "order.created", props, body);

// Spring AMQP: set via MessagePostProcessor
rabbitTemplate.convertAndSend("orders.exchange", "order.created", order, msg -> {
    msg.getMessageProperties().setExpiration("30000");
    return msg;
});

// @RabbitListener producer side with MessageProperties
@Bean
public MessageConverter converter() {
    return new Jackson2JsonMessageConverter();
}

// Priority message with short TTL (urgent, expires fast if not consumed)
rabbitTemplate.convertAndSend("orders.exchange", "order.urgent", urgentOrder, msg -> {
    msg.getMessageProperties().setExpiration("5000");   // 5s — if not consumed, dead-letter
    msg.getMessageProperties().setPriority(10);
    return msg;
});

Retry with Delay Pattern Using TTL + DLX

A common pattern for delayed retries: reject a failed message (dead-letter it) to a "wait queue" with a `x-message-ttl` of the desired delay and `x-dead-letter-exchange` pointing back to the original exchange. After the TTL expires, the message is automatically re-routed to the original processing queue — implementing retry-with-backoff without a plugin.

RabbitMQ — retry-with-delay using TTL + DLX
// Retry-with-delay via TTL + DLX:
//
// Processing queue → (on failure, basicNack requeue=false) →
//   DLX → wait.queue (TTL=10s, DLX=original exchange) →
//   (after 10s) → original exchange → processing queue
//
// This creates a 10-second retry delay without any application timer

@Bean
Queue waitQueue() {
    return QueueBuilder.durable("orders.wait.10s")
        .withArgument("x-message-ttl",             10_000)        // 10s delay
        .withArgument("x-dead-letter-exchange",    "orders.topic") // back to source
        .withArgument("x-dead-letter-routing-key", "order.created")
        .build();
}

// Tip: create multiple wait queues for exponential backoff:
// orders.wait.10s  (x-message-ttl=10000)
// orders.wait.30s  (x-message-ttl=30000)
// orders.wait.60s  (x-message-ttl=60000)
// Track retry count via x-death header and route to the appropriate wait queue

Key Points to Remember

  • 1x-message-ttl (queue declaration) applies a uniform TTL to all messages in that queue
  • 2expiration (per-message property, set by producer) allows different TTLs per message
  • 3Queue-level TTL only expires messages at the queue HEAD — not mid-queue
  • 4Expired messages are dead-lettered to x-dead-letter-exchange or silently dropped
  • 5TTL + DLX enables retry-with-delay: wait queues with TTL automatically re-route after expiry
  • 6For exponential backoff: use multiple wait queues (10s, 30s, 60s) and route based on x-death count

Interview Questions

Sign in to ask Aria
1

What is the difference between queue-level TTL and per-message TTL in RabbitMQ?

EasyThoughtWorks
2

What happens to a message when it expires and no DLX is configured?

EasyAmazon
3

How would you implement a 30-second retry delay using TTL and DLX without a plugin?

MediumZalando
4

Why may a per-message TTL not expire a message exactly at its TTL time?

HardBooking.com
5

How would you implement exponential backoff retries in RabbitMQ?

HardDeliveroo

Ask Aria about Message TTL

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…