How to Implement Delayed Message Delivery with RabbitMQ’s Plugin in Spring Boot
This article explains why traditional delay solutions like Redis expiration, database polling, or JVM DelayQueue are inefficient, then demonstrates step‑by‑step how to configure RabbitMQ’s delayed‑queue plugin, create exchanges, queues, and bindings in Spring Boot, and send and consume delayed messages with code examples.
Background
Many applications require delayed message delivery, e.g., order‑confirmation after a fixed period or ticket‑reservation timeouts. Traditional solutions such as Redis TTL, database polling, or JVM DelayQueue cause high memory pressure, excessive I/O, or lack persistence.
RabbitMQ Delayed‑Queue Plugin
Since RabbitMQ 3.6.x an official delayed‑queue plugin is available. After placing the plugin file in the plugins directory and enabling it, a queue can hold a message for a configurable delay before routing it to consumers.
Spring Boot Configuration
Define a delayed exchange, a durable queue, and a binding in a @Configuration class. The exchange must be declared with the argument x-delayed-message=topic or by calling exchange.setDelayed(true).
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class MQConfig {
public static final String LAZY_EXCHANGE = "Ex.LazyExchange";
public static final String LAZY_QUEUE = "MQ.LazyQueue";
public static final String LAZY_KEY = "lazy.#";
@Bean
public TopicExchange lazyExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-message", "topic");
TopicExchange exchange = new TopicExchange(LAZY_EXCHANGE, true, false, args);
exchange.setDelayed(true); // enable delayed routing
return exchange;
}
@Bean
public Queue lazyQueue() {
return new Queue(LAZY_QUEUE, true);
}
@Bean
public Binding lazyBinding() {
return BindingBuilder.bind(lazyQueue()).to(lazyExchange()).with(LAZY_KEY);
}
}Sending Delayed Messages
When publishing, use a MessagePostProcessor to set the x-delay header (or call msg.getMessageProperties().setDelay(milliseconds)). The message should be marked persistent.
import com.anqi.mq.config.MQConfig;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MQSender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendLazy(Object payload) {
rabbitTemplate.setMandatory(true);
// confirmCallback and returnCallback can be set as needed
CorrelationData correlationData = new CorrelationData("msg-" + new Date().getTime());
rabbitTemplate.convertAndSend(
MQConfig.LAZY_EXCHANGE,
"lazy.boot",
payload,
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message msg) throws AmqpException {
msg.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
// delay in milliseconds, e.g., 6000 ms (6 s)
msg.getMessageProperties().setDelay(6000);
return msg;
}
},
correlationData);
}
}Consuming Delayed Messages
A listener annotated with @RabbitListener receives messages from the delayed queue after the configured interval. The listener should acknowledge the message.
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class MQReceiver {
@RabbitListener(queues = "MQ.LazyQueue")
@RabbitHandler
public void onLazyMessage(Message msg, Channel channel) throws IOException {
long deliveryTag = msg.getMessageProperties().getDeliveryTag();
channel.basicAck(deliveryTag, false);
System.out.println("lazy receive " + new String(msg.getBody()));
}
}Test Verification
A simple JUnit test sends a message with a 6‑second delay and verifies that the consumer prints the payload after the expected interval.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@SpringBootTest
@RunWith(SpringRunner.class)
public class MQSenderTest {
@Autowired
private MQSender mqSender;
@Test
public void sendLazy() throws Exception {
String msg = "hello spring boot";
mqSender.sendLazy(msg + ":");
}
}Running the test prints lazy receive hello spring boot: after approximately six seconds, confirming that the delayed‑queue plugin provides reliable, persistent delayed messaging without the drawbacks of Redis TTL, database polling, or in‑memory queues.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
