A dead letter queue (DLQ) is a special queue that holds messages that cannot be processed successfully. It helps keep your main queue clean and lets you inspect or fix problem messages later.
Dead letter queues in Spring Boot
@Configuration public class RabbitConfig { public static final String MAIN_QUEUE = "main.queue"; public static final String DEAD_LETTER_QUEUE = "dead.letter.queue"; public static final String EXCHANGE = "main.exchange"; @Bean Queue mainQueue() { return QueueBuilder.durable(MAIN_QUEUE) .withArgument("x-dead-letter-exchange", "") .withArgument("x-dead-letter-routing-key", DEAD_LETTER_QUEUE) .build(); } @Bean Queue deadLetterQueue() { return QueueBuilder.durable(DEAD_LETTER_QUEUE).build(); } @Bean DirectExchange exchange() { return new DirectExchange(EXCHANGE); } @Bean Binding mainBinding() { return BindingBuilder.bind(mainQueue()).to(exchange()).with(MAIN_QUEUE); } @Bean Binding deadLetterBinding() { return BindingBuilder.bind(deadLetterQueue()).to(new DirectExchange("")).with(DEAD_LETTER_QUEUE); } }
The x-dead-letter-exchange argument tells RabbitMQ where to send messages that are rejected or expire.
The x-dead-letter-routing-key sets the routing key for the dead letter queue.
@Bean
Queue mainQueue() {
return QueueBuilder.durable("main.queue")
.withArgument("x-dead-letter-exchange", "")
.withArgument("x-dead-letter-routing-key", "dead.letter.queue")
.build();
}@Bean
Queue deadLetterQueue() {
return QueueBuilder.durable("dead.letter.queue").build();
}@Bean
Binding deadLetterBinding() {
return BindingBuilder.bind(deadLetterQueue()).to(new DirectExchange("")).with("dead.letter.queue");
}This Spring Boot app configures a main queue with a dead letter queue. The listener throws an error for messages containing "fail". Those messages go to the dead letter queue and are logged separately.
package com.example.demo; import org.springframework.amqp.core.*; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } @Configuration class RabbitConfig { public static final String MAIN_QUEUE = "main.queue"; public static final String DEAD_LETTER_QUEUE = "dead.letter.queue"; public static final String EXCHANGE = "main.exchange"; @Bean Queue mainQueue() { return QueueBuilder.durable(MAIN_QUEUE) .withArgument("x-dead-letter-exchange", "") .withArgument("x-dead-letter-routing-key", DEAD_LETTER_QUEUE) .build(); } @Bean Queue deadLetterQueue() { return QueueBuilder.durable(DEAD_LETTER_QUEUE).build(); } @Bean DirectExchange exchange() { return new DirectExchange(EXCHANGE); } @Bean Binding mainBinding() { return BindingBuilder.bind(mainQueue()).to(exchange()).with(MAIN_QUEUE); } @Bean Binding deadLetterBinding() { return BindingBuilder.bind(deadLetterQueue()).to(new DirectExchange("")).with(DEAD_LETTER_QUEUE); } } @Component class MessageListener { @RabbitListener(queues = RabbitConfig.MAIN_QUEUE) public void listen(String message) { System.out.println("Received message: " + message); if (message.contains("fail")) { System.out.println("Message processing failed, rejecting message."); throw new RuntimeException("Processing failed"); } System.out.println("Message processed successfully."); } } @Component class DeadLetterListener { @RabbitListener(queues = RabbitConfig.DEAD_LETTER_QUEUE) public void listenDeadLetter(String message) { System.out.println("Dead letter received: " + message); } }
Time complexity: Message routing is O(1) in RabbitMQ.
Space complexity: Dead letter queue stores failed messages separately, so plan storage accordingly.
Common mistake: Forgetting to set both x-dead-letter-exchange and x-dead-letter-routing-key causes messages not to route to DLQ.
Use dead letter queues when you want to isolate and inspect failed messages instead of losing or blocking them.
Dead letter queues hold messages that fail processing to keep main queues clean.
Configure DLQ by setting queue arguments x-dead-letter-exchange and x-dead-letter-routing-key.
Use DLQs to analyze, retry, or fix problem messages without blocking your system.