Implementing Delayed Queues with RabbitMQ in Spring Boot
This article explains how to implement scheduled tasks in a Spring Boot application using RabbitMQ delayed queues, detailing the use of dead‑letter exchanges, message TTL settings, queue configuration, and Java code examples for publishing and consuming delayed messages.
In many e‑commerce systems, scheduled tasks such as coupon expiration or order cancellation are needed, and traditional polling of the database can become inefficient at scale.
RabbitMQ does not provide a native delayed queue, but by using a dead‑letter exchange together with message TTL (time‑to‑live), a delay mechanism can be built.
The dead‑letter exchange receives messages whose TTL has expired or that are rejected; the message then routes to a second queue where it is finally processed.
Configuration steps include creating a normal exchange (named delay ), an auto‑expire queue (e.g., delay_queue1 ) with x-message-ttl and x-dead-letter-exchange arguments, and a processing queue ( delay_queue2 ) bound to the dead‑letter exchange.
Sample Java code (Spring Boot) shows how to publish a message with an expiration property and how to set up connection factories, exchanges, queues, bindings, and a listener container that manually acknowledges messages from the processing queue.
byte[] messageBodyBytes = "Hello world!".getBytes();
AMQP.BasicProperties properties = new AMQP.BasicProperties();
properties.setExpiration("60000");
channel.basicPublish("my-exchange", "queue-key", properties, messageBodyBytes);Another snippet demonstrates the Spring configuration of the delayed‑queue infrastructure:
@Configuration
public class DelayQueue {
public static final String EXCHANGE = "delay";
public static final String ROUTINGKEY1 = "delay";
public static final String ROUTINGKEY2 = "delay_key";
@Bean
ConnectionFactory connectionFactory() {
CachingConnectionFactory cf = new CachingConnectionFactory("120.76.237.8", 5672);
cf.setUsername("kberp");
cf.setPassword("kberp");
cf.setVirtualHost("/");
cf.setPublisherConfirms(true);
return cf;
}
@Bean
DirectExchange defaultExchange() {
return new DirectExchange(EXCHANGE, true, false);
}
@Bean
Queue queue() {
return new Queue("delay_queue2", true);
}
@Bean
@Autowired
Binding binding() {
return BindingBuilder.bind(queue()).to(defaultExchange()).with(DelayQueue.ROUTINGKEY2);
}
@Bean
@Autowired
SimpleMessageListenerContainer messageContainer2(ConnectionFactory connectionFactory) {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer(connectionFactory);
container.setQueues(queue());
container.setAcknowledgeMode(AcknowledgeMode.MANUAL);
container.setMessageListener((Message message, com.rabbitmq.client.Channel channel) -> {
byte[] body = message.getBody();
System.out.println("delay_queue2 received: " + new String(body));
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
});
return container;
}
}By sending tasks as delayed messages, the system avoids frequent database scans, reducing I/O load while ensuring timely execution of scheduled operations.
IT Architects Alliance
Discussion and exchange on system, internet, large‑scale distributed, high‑availability, and high‑performance architectures, as well as big data, machine learning, AI, and architecture adjustments with internet technologies. Includes real‑world large‑scale architecture case studies. Open to architects who have ideas and enjoy sharing.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.