Handling Dead Letter Queues in RabbitMQ with Spring Boot
This article explains what dead letters are in RabbitMQ, outlines their common causes, and provides a complete Spring Boot configuration and code examples for setting up dead‑letter exchanges, queues, TTL handling, message rejection, and consumer processing to reliably manage undeliverable messages.
Dead letters are messages that cannot be consumed and are routed to a special dead‑letter queue (DLX) via a dead‑letter‑exchange.
Sources of dead letters
Message TTL expiration
Queue reaching its maximum length
Message rejection using basic.reject or basic.nack with requeue = false
To handle dead letters, the article sets up normal and dead‑letter exchanges and queues using Spring Boot configuration.
@Configuration
public class DeadConfig {
// Normal exchange
@Bean
DirectExchange normalExchange() {
return new DirectExchange("normalExchange", true, false);
}
// Normal queue (max length 5) with dead‑letter arguments
@Bean
Queue normalQueue() {
Map
args = deadQueueArgs();
args.put("x-max-length", 5);
return new Queue("normalQueue", true, false, false, args);
}
// TTL queue (message TTL 60 seconds) without consumers
@Bean
Queue ttlQueue() {
Map
args = deadQueueArgs();
args.put("x-message-ttl", 60 * 1000);
return new Queue("ttlQueue", true, false, false, args);
}
// Bindings for normal and TTL queues
@Bean
Binding normalRouteBinding() {
return BindingBuilder.bind(normalQueue()).to(normalExchange()).with("normalRouting");
}
@Bean
Binding ttlRouteBinding() {
return BindingBuilder.bind(ttlQueue()).to(normalExchange()).with("ttlRouting");
}
// Dead‑letter exchange and queue
@Bean
DirectExchange deadExchange() {
return new DirectExchange("deadExchange", true, false);
}
@Bean
Queue deadQueue() {
return new Queue("deadQueue", true, false, false);
}
@Bean
Binding deadRouteBinding() {
return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("deadRouting");
}
// Arguments that bind a queue to the dead‑letter exchange
private Map
deadQueueArgs() {
Map
map = new HashMap<>();
map.put("x-dead-letter-exchange", "deadExchange");
map.put("x-dead-letter-routing-key", "deadRouting");
return map;
}
}Sending messages to the normal queue (max length 5) is demonstrated with a REST endpoint that creates a map containing a UUID and timestamp, then publishes it using rabbitTemplate.convertAndSend .
@GetMapping("/normalQueue")
public String normalQueue() {
Map
map = new HashMap<>();
map.put("messageId", UUID.randomUUID().toString());
map.put("data", System.currentTimeMillis() + ", normal queue message, max length 5");
rabbitTemplate.convertAndSend("normalExchange", "normalRouting", map, new CorrelationData());
return JSONObject.toJSONString(map);
}A second endpoint shows how to send a message that will expire after 60 seconds, causing it to be dead‑lettered.
@GetMapping("/ttlToDead")
public String ttlToDead() {
Map
map = new HashMap<>();
map.put("messageId", UUID.randomUUID().toString());
map.put("data", System.currentTimeMillis() + ", ttl queue message");
rabbitTemplate.convertAndSend("normalExchange", "ttlRouting", map, new CorrelationData());
return JSONObject.toJSONString(map);
}The consumer for the normal queue deliberately rejects messages without re‑queueing, triggering dead‑letter routing.
@Component
@RabbitListener(queues = "normalQueue")
public class NormalConsumer {
@RabbitHandler
public void process(Map message, Channel channel, Message mqMsg) throws IOException {
System.out.println("Received and reject without requeue: " + message);
channel.basicReject(mqMsg.getMessageProperties().getDeliveryTag(), false);
}
}The dead‑letter queue consumer acknowledges messages, confirming they have been successfully routed.
@Component
@RabbitListener(queues = "deadQueue")
public class DeadConsumer {
@RabbitHandler
public void process(Map message, Channel channel, Message mqMsg) throws IOException {
System.out.println("Dead queue received message: " + message);
channel.basicAck(mqMsg.getMessageProperties().getDeliveryTag(), false);
}
}Running the demo shows messages from the normal queue being dead‑lettered when the queue exceeds its length, when TTL expires, or when the consumer rejects them, with the dead‑letter queue receiving and logging each message.
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.