Home/Learn/RabbitMQ/Delayed Message Exchange

Delayed Message Exchange

Intermediate
Advanced Queue Types

The 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.

Java — CustomExchange for delayed message plugin
# 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.

Java — publishing with x-delay header
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.

Java — TTL+DLX retry as HA alternative to delayed-message plugin
// 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 Aria
1

What is the rabbitmq_delayed_message_exchange plugin and what problem does it solve?

MediumPivotal
2

What is the main HA limitation of the delayed message exchange?

HardZalando
3

How would you implement a retry-after-delay without the delayed message plugin?

HardAmazon
4

What header triggers the delay and in what unit?

EasyTCS
5

How do you declare an x-delayed-message exchange in Spring AMQP?

MediumInfosys

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.

Loading discussion…