Mastering RabbitMQ Dead Letter Exchanges and Queues with Java Code
This article explains the concepts of RabbitMQ dead‑letter exchanges and queues, outlines why messages become dead letters, and provides complete Java code examples for configuring exchanges, publishing messages with TTL, and consuming both normal and dead‑letter queues.
Dead Letter Exchange (DLX)
Definition: A special exchange that receives messages that become dead letters from other queues. It works like any RabbitMQ exchange, routing messages according to binding rules.
Purpose: Provides a centralized entry point for dead letters, allowing uniform handling and preventing data loss.
Dead Letter Queue (DLQ)
Definition: A queue that stores messages that cannot be processed in the normal flow, i.e., dead letters. These messages can be analyzed, retried, or handled specially.
Reasons for dead letters:
Message rejected and not requeued: Consumer calls basic.reject or basic.nack with requeue set to false, causing the message to become a dead letter.
Message expiration: Setting a TTL (Time‑To‑Live) for a message or a queue; when the TTL expires, the message is dead‑lettered.
Queue reaches maximum length: When a queue is declared with max-length and the number of messages hits this limit, new messages are discarded as dead letters.
Code Example
The following Java code demonstrates how a message expires and is routed to a dead‑letter queue.
Initialize RabbitMQ connection, queues and exchanges
/** * RabbitMQ configuration class * Manages connection, queue and exchange declarations */ @Slf4j public class RabbitMQConfig { public static final String NORMAL_QUEUE = "normal.queue"; public static final String DLX_QUEUE = "dlx.queue"; public static final String NORMAL_EXCHANGE = "normal.exchange"; public static final String DLX_EXCHANGE = "dlx.exchange"; public static final String NORMAL_ROUTING_KEY = "normal.routing.key"; public static final String DLX_ROUTING_KEY = "dlx.routing.key"; /* createConnection and init methods omitted for brevity */ }Message producer
@Slf4j public class MessageProducer { /** Send a message to the normal queue */ public void sendMessage(String message) throws Exception { try (Connection connection = RabbitMQConfig.createConnection(); Channel channel = connection.createChannel()) { log.info("Sending message: {}", message); channel.basicPublish(RabbitMQConfig.NORMAL_EXCHANGE, RabbitMQConfig.NORMAL_ROUTING_KEY, null, message.getBytes()); } } /** Send a message with TTL */ public void sendMessageWithTTL(String message, int ttl) throws Exception { try (Connection connection = RabbitMQConfig.createConnection(); Channel channel = connection.createChannel()) { AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().expiration(String.valueOf(ttl)).build(); log.info("Sending message: {}, TTL: {}ms", message, ttl); channel.basicPublish(RabbitMQConfig.NORMAL_EXCHANGE, RabbitMQConfig.NORMAL_ROUTING_KEY, properties, message.getBytes()); } } }Message consumer
@Slf4j public class MessageConsumer { /** Consume messages from the normal queue */ public void consumeNormalQueue() throws Exception { Connection connection = RabbitMQConfig.createConnection(); Channel channel = connection.createChannel(); channel.basicQos(1); DeliverCallback deliverCallback = (consumerTag, delivery) -> { String msg = new String(delivery.getBody(), StandardCharsets.UTF_8); log.info("Received normal queue message: {}", msg); try { Thread.sleep(20000); } catch (InterruptedException e) { throw new RuntimeException(e); } channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); }; channel.basicConsume(RabbitMQConfig.NORMAL_QUEUE, false, deliverCallback, consumerTag -> {}); } /** Consume messages from the dead‑letter queue */ public void consumeDlxQueue() throws Exception { Connection connection = RabbitMQConfig.createConnection(); Channel channel = connection.createChannel(); channel.basicQos(1); DeliverCallback deliverCallback = (consumerTag, delivery) -> { String msg = new String(delivery.getBody(), StandardCharsets.UTF_8); log.info("Received dead‑letter queue message: {}", msg); channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false); }; channel.basicConsume(RabbitMQConfig.DLX_QUEUE, false, deliverCallback, consumerTag -> {}); } }Test
@Slf4j public class DLXTest { private static final int THREAD_COUNT = 1; private static final int MESSAGE_COUNT = 2; public static void main(String[] args) throws Exception { RabbitMQConfig.init(); MessageProducer producer = new MessageProducer(); MessageConsumer consumer = new MessageConsumer(); new Thread(() -> { try { consumer.consumeNormalQueue(); } catch (Exception e) { log.error("Normal queue consumer error", e); } }).start(); new Thread(() -> { try { consumer.consumeDlxQueue(); } catch (Exception e) { log.error("Dead‑letter queue consumer error", e); } }).start(); ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT); CountDownLatch latch = new CountDownLatch(THREAD_COUNT); for (int i = 0; i < THREAD_COUNT; i++) { executor.submit(() -> { try { for (int j = 0; j < MESSAGE_COUNT; j++) { int ttl = (int) (Math.random() * 30000) + 1000; String msg = String.format("Message‑thread%d‑%d (TTL: %dms)", i + 1, j + 1, ttl); producer.sendMessageWithTTL(msg, ttl); Thread.sleep((long) (Math.random() * 100)); } } catch (Exception e) { log.error("Send message error", e); } finally { latch.countDown(); } }); } latch.await(); log.info("All messages sent"); executor.shutdown(); executor.awaitTermination(1, TimeUnit.MINUTES); Thread.sleep(60000); log.info("Test completed"); } }From the test results, the first message (TTL = 28301 ms) is consumed by the normal consumer, while the second message (TTL = 4332 ms) expires before the first finishes processing and is routed to the dead‑letter queue, as shown by the ~4 second gap in the logs.
Xuanwu Backend Tech Stack
Primarily covers fundamental Java concepts, mainstream frameworks, deep dives into underlying principles, and JVM internals.
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.
