Queue TTL & Auto-Expiry
Intermediatex-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).
// 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.
// 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.
// 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 AriaWhat is the difference between x-message-ttl and x-expires in RabbitMQ?
What happens to a message when it expires in a queue with no dead-letter exchange?
How would you implement a 5-minute retry delay in RabbitMQ without a plugin?
Why are temporary reply queues in RPC patterns a memory leak risk and how do you prevent it?
How do you detect the number of times a message has been dead-lettered?
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.