Delayed Message Exchange
IntermediateThe community delayed-message plugin stores messages and delivers them after a configurable x-delay (ms), enabling scheduled tasks and retry-after-delay patterns.
Overview
The rabbitmq_delayed_message_exchange community plugin adds a new exchange type (x-delayed-message) that holds messages internally until the x-delay header's millisecond interval elapses, then routes them to bound queues normally. This enables retry-after-delay, scheduled notifications, and deferred task execution without an external scheduler. Key limitations: delayed messages are stored in memory and on disk on a single node — they are not replicated to other cluster nodes, so a node failure can lose all pending delayed messages. For production HA, consider the TTL+DLX retry pattern instead.
Enabling and Declaring the Delayed Exchange
The plugin must be installed on the broker. In Spring AMQP, declare a CustomExchange with type x-delayed-message and the underlying routing type as an argument.
# Install the plugin on the broker
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
# Spring AMQP bean declaration
@Bean
public CustomExchange delayedExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct"); // underlying routing type
return new CustomExchange(
"orders.delayed", // exchange name
"x-delayed-message",// exchange type
true, // durable
false, // auto-delete
args
);
}
@Bean
public Queue scheduledOrderQueue() {
return QueueBuilder.durable("orders.scheduled").build();
}
@Bean
public Binding delayedBinding(Queue scheduledOrderQueue, CustomExchange delayedExchange) {
return BindingBuilder.bind(scheduledOrderQueue)
.to(delayedExchange).with("order.scheduled").noargs();
}Publishing with x-delay Header
Set the x-delay header to the number of milliseconds the exchange should hold the message before routing it. The message passes through to bound queues only after the delay.
public void scheduleOrder(OrderEvent event, long delayMs) {
rabbitTemplate.convertAndSend("orders.delayed", "order.scheduled", event, message -> {
message.getMessageProperties().setHeader("x-delay", delayMs);
return message;
});
log.info("Order {} scheduled for delivery in {}ms", event.getOrderId(), delayMs);
}
// Retry-after-delay pattern: re-publish with exponential backoff
public void retryWithDelay(OrderEvent event, int attempt) {
long delay = (long) Math.pow(2, attempt) * 1000; // 2s, 4s, 8s...
if (attempt < 5) {
scheduleOrder(event, delay);
} else {
log.error("Max retries reached for order: {}", event.getOrderId());
}
}Alternative: TTL + DLX Retry Loop
For HA clusters without the plugin, the TTL+DLX pattern achieves delayed retry. Messages expire from a wait queue after x-message-ttl ms and are dead-lettered back to the main processing exchange — no plugin required.
// Wait queue: messages sit here for TTL ms then go to dead-letter exchange
@Bean
public Queue waitQueue() {
return QueueBuilder.durable("orders.wait")
.withArgument("x-message-ttl", 30_000) // 30s wait
.withArgument("x-dead-letter-exchange", "orders") // then re-route here
.withArgument("x-dead-letter-routing-key", "order.retry")
.build();
}
// Consumer nacks and republishes to wait queue on failure
@RabbitListener(queues = "orders.processing")
public void onOrder(OrderEvent event, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
try {
process(event);
channel.basicAck(tag, false);
} catch (Exception e) {
channel.basicNack(tag, false, false); // send to DLX → wait queue → retry
}
}Key Points to Remember
- 1x-delayed-message exchange holds messages internally until x-delay ms elapses, then routes normally
- 2Requires the community rabbitmq_delayed_message_exchange plugin — not bundled by default
- 3Delayed messages are stored on a single node — NOT replicated; node failure loses pending delays
- 4For HA clusters, prefer the TTL+DLX retry pattern which uses standard replicated queues
- 5x-delay header value is in milliseconds; maximum useful delay is limited by memory on the node
- 6The underlying routing type (x-delayed-type: direct/topic/fanout) is set as a declaration argument
Interview Questions
Sign in to ask AriaWhat is the rabbitmq_delayed_message_exchange plugin and what problem does it solve?
What is the main HA limitation of the delayed message exchange?
How would you implement a retry-after-delay without the delayed message plugin?
What header triggers the delay and in what unit?
How do you declare an x-delayed-message exchange in Spring AMQP?
Ask Aria about Delayed Message Exchange
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.