0
0
Spring Bootframework~5 mins

Dead letter queues in Spring Boot

Choose your learning style9 modes available
Introduction

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.

When a message fails to process after several retries and you want to isolate it.
When you want to analyze why certain messages are causing errors without losing them.
When you want to avoid blocking the main message flow due to bad messages.
When you want to implement a retry mechanism with fallback for failed messages.
When you want to monitor and alert on problematic messages in your system.
Syntax
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.

Examples
This creates a durable main queue with a dead letter queue configured.
Spring Boot
@Bean
Queue mainQueue() {
    return QueueBuilder.durable("main.queue")
        .withArgument("x-dead-letter-exchange", "")
        .withArgument("x-dead-letter-routing-key", "dead.letter.queue")
        .build();
}
This creates a durable dead letter queue to hold failed messages.
Spring Boot
@Bean
Queue deadLetterQueue() {
    return QueueBuilder.durable("dead.letter.queue").build();
}
This binds the dead letter queue to the default exchange with its own name as routing key.
Spring Boot
@Bean
Binding deadLetterBinding() {
    return BindingBuilder.bind(deadLetterQueue()).to(new DirectExchange("")).with("dead.letter.queue");
}
Sample Program

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.

Spring Boot
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);
    }
}
OutputSuccess
Important Notes

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.

Summary

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.