Home/Learn/RabbitMQ/Queue TTL & Auto-Expiry

Queue TTL & Auto-Expiry

Intermediate
Dead-Lettering

x-expires deletes an unused queue after a given idle period; useful for temporary reply queues and ephemeral work queues that should not accumulate indefinitely.

Overview

RabbitMQ supports two TTL mechanisms: per-message TTL (x-message-ttl) — the maximum time a message may remain in a queue before it expires and is dead-lettered; and queue expiry (x-expires) — the queue itself is deleted if no consumer or producer accesses it for the configured idle period. Combined with a Dead Letter Exchange, expired messages can be routed for logging, retry, or alerting instead of being silently dropped.

Per-Message TTL (x-message-ttl)

x-message-ttl on the queue sets a default TTL for all messages in the queue. Individual message expiration can also be set per-message via the expiration property (in milliseconds as a string).

Java — per-message TTL with DLX
// Queue-level TTL — all messages expire after 60 seconds
@Bean
public Queue orderQueue() {
    return QueueBuilder.durable("orders.processing")
        .withArgument("x-message-ttl", 60_000)     // 60 000 ms = 60 s
        .withArgument("x-dead-letter-exchange", "orders.dlx")   // where expired messages go
        .withArgument("x-dead-letter-routing-key", "orders.expired")
        .build();
}

// Per-message TTL — override on individual messages
rabbitTemplate.convertAndSend("orders", "order.placed", event, msg -> {
    msg.getMessageProperties().setExpiration("30000");  // 30 s (string, not int)
    return msg;
});

// Note: when BOTH queue TTL and per-message expiration are set,
// the LOWER of the two values wins (whichever expires first)

Queue Auto-Expiry (x-expires)

x-expires deletes the entire queue if it has no consumers and no messages are published to it for the configured duration. Ideal for temporary reply queues in RPC patterns that should not accumulate on the broker.

Java — queue auto-expiry for temporary queues
// Temporary reply queue — auto-deleted after 30 minutes of inactivity
@Bean
public Queue replyQueue() {
    return QueueBuilder.nonDurable("rpc.reply." + UUID.randomUUID())
        .withArgument("x-expires", 1_800_000)   // 30 minutes in ms
        .build();
}

// RPC pattern with temporary reply queue
String replyQueueName = "rpc.reply." + UUID.randomUUID();
rabbitAdmin.declareQueue(new Queue(replyQueueName, false, true, true,
    Map.of("x-expires", 300_000)));  // 5 min — auto-cleaned up

// Also useful for cleanup of orphaned work queues:
// e.g., per-user session queues that should expire when the session ends
@Bean
public Queue userSessionQueue(String userId) {
    return QueueBuilder.durable("session." + userId)
        .withArgument("x-expires", 3_600_000)   // 1 hour
        .build();
}

Combining TTL + DLX for Retry Delays

A common pattern: messages expire from a "wait" queue (with x-message-ttl) and are dead-lettered back to the main exchange for reprocessing — implementing a delay/retry without a plugin.

Java — TTL + DLX retry loop with retry count check
// Retry-with-delay pattern using TTL + DLX (no plugin required)

// Main processing queue → DLX sends failures to wait queue
@Bean public Queue processingQueue() {
    return QueueBuilder.durable("orders.processing")
        .withArgument("x-dead-letter-exchange", "orders.wait")
        .build();
}

// Wait queue — holds failed messages for 5 minutes then re-routes to main exchange
@Bean public Queue waitQueue() {
    return QueueBuilder.durable("orders.wait")
        .withArgument("x-message-ttl",           300_000)   // 5 min delay
        .withArgument("x-dead-letter-exchange",  "orders")  // back to main exchange
        .withArgument("x-dead-letter-routing-key","order.placed") // original routing key
        .build();
}

// Flow:
// 1. Consumer processes from orders.processing and throws exception
// 2. Message nacked with requeue=false → DLX "orders.wait"
// 3. Message sits in orders.wait for 5 minutes (TTL)
// 4. TTL expires → DLX "orders" → back to orders.processing
// 5. Consumer retries the message

// Limit retry loops with x-death header count in consumer:
@RabbitListener(queues = "orders.processing")
public void process(Message message) {
    long retryCount = Optional.ofNullable(
        (List<?>) message.getMessageProperties()
            .getHeaders().get("x-death"))
        .map(List::size).orElse(0);
    if (retryCount >= 3) {
        // Exceeded max retries — move to permanent DLQ
        rabbitTemplate.send("orders.permanent-dlq", message);
        return;
    }
    // ... process
}

Key Points to Remember

  • 1x-message-ttl sets per-queue default TTL for messages in milliseconds.
  • 2Individual messages can override TTL via expiration property (string of ms).
  • 3When both queue TTL and per-message expiration are set, the lower value wins.
  • 4x-expires deletes the entire queue after an idle period — great for temporary reply queues.
  • 5Expired messages are dead-lettered to the x-dead-letter-exchange if configured, otherwise dropped.
  • 6TTL + DLX enables delay/retry patterns without requiring the delayed-message plugin.

Interview Questions

Sign in to ask Aria
1

What is the difference between x-message-ttl and x-expires in RabbitMQ?

MediumAmazon
2

What happens to a message when it expires in a queue with no dead-letter exchange?

EasyPivotal
3

How would you implement a 5-minute retry delay in RabbitMQ without a plugin?

HardNetflix
4

Why are temporary reply queues in RPC patterns a memory leak risk and how do you prevent it?

MediumRevolut
5

How do you detect the number of times a message has been dead-lettered?

HardUber

Ask Aria about Queue TTL & Auto-Expiry

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…