Dead Letter Policy
Configure a dead-letter policy on a KubeMQ Queue stream with the Java SDK to route repeatedly failed messages.
Which to use
For the task-oriented how-to, see Dead Letter Queue. This page focuses on the attemptsBeforeDeadLetterQueue/deadLetterQueue policy option itself — its evaluation point and interaction with redelivery.
Overview
A dead-letter policy protects a queue from poison messages — a record that fails processing over and over because of a malformed payload, a consumer bug, or a downstream dependency that is down. Without one, that message is redelivered forever: it blocks head-of-line delivery, burns your consumers' retry budget, and can stall an entire queue behind a single bad record.
With a policy attached, KubeMQ counts each failed delivery and, once the message crosses attemptsBeforeDeadLetterQueue, automatically moves it to the dead-letter channel you name with deadLetterQueue. The main queue keeps flowing while the failure is quarantined for inspection or replay.
Gotchas: the receive count increments on every failed delivery — an explicit reject(), an expired transaction, or a visibility timeout — not just deliberate rejections, so set the ceiling above your normal retry budget. The dead-letter channel is an ordinary queue with no special behavior: nothing drains it for you, so monitor it and build a reprocessing path or failures pile up silently. The policy is set at send time and travels with the message, so the producer, not the consumer, decides the retry ceiling.
Prerequisites
- KubeMQ server running on
localhost:50000 - Java SDK installed (
implementation 'io.kubemq.sdk:kubemq-sdk-Java:3.1.1'(Gradle) or Maven dependency from Getting Started)
Code
package io.kubemq.example.queuesstream;
import io.kubemq.sdk.queues.*;
import java.util.UUID;
/**
* DeadLetterPolicyExample for Queues Stream
*/
public class DeadLetterPolicyExample {
private static final String ADDRESS = "localhost:50000";
private static final String CLIENT_ID = "java-queues-dead-letter-policy-client";
private static final String CHANNEL = "java-queues.dead-letter-policy";
private static final String DLQ = "java-queues.dead-letter-policy-dlq";
/**
* Demonstrates configuring dead letter policy with max attempts
* and automatic routing to a dead letter queue.
*/
public static void main(String[] args) throws InterruptedException {
// Create a client connected to the KubeMQ server
try (QueuesClient client = QueuesClient.builder().address(ADDRESS).clientId(CLIENT_ID).build()) {
// Create main queue and dead letter queue channels
client.createQueuesChannel(CHANNEL);
client.createQueuesChannel(DLQ);
// Send a message with DLQ policy (moves to DLQ after 2 rejections)
client.sendQueueMessage(QueueMessage.builder()
.id(UUID.randomUUID().toString()).channel(CHANNEL)
.body("Poison message".getBytes())
.attemptsBeforeDeadLetterQueue(2).deadLetterQueue(DLQ).build());
System.out.println("Sent message with DLQ policy (max 2 attempts).\n");
// Reject the message twice; on third attempt it should be in DLQ
for (int attempt = 1; attempt <= 3; attempt++) {
QueuesPollResponse resp = client.receiveQueueMessages(QueuesPollRequest.builder()
.channel(CHANNEL).pollMaxMessages(1).pollWaitTimeoutInSeconds(2).build());
if (!resp.getMessages().isEmpty()) {
System.out.println("Attempt " + attempt + ": Rejecting...");
resp.getMessages().get(0).reject();
} else {
System.out.println("Attempt " + attempt + ": No message (moved to DLQ).");
break;
}
Thread.sleep(500);
}
// Read the message from the dead letter queue
QueuesPollResponse dlqResp = client.receiveQueueMessages(QueuesPollRequest.builder()
.channel(DLQ).pollMaxMessages(1).pollWaitTimeoutInSeconds(2).autoAckMessages(true).build());
if (!dlqResp.getMessages().isEmpty()) {
System.out.println("\nDLQ message: " + new String(dlqResp.getMessages().get(0).getBody()));
}
// Clean up resources
client.deleteQueuesChannel(CHANNEL);
client.deleteQueuesChannel(DLQ);
}
}
}
How It Works
attemptsBeforeDeadLetterQueue(2)on theQueueMessagesets a per-message retry cap; after tworeject()calls the broker moves the message to the channel indeadLetterQueue(DLQ).- The retry loop runs three iterations: the first two reject the message; on the third iteration the main queue is empty (message already in DLQ).
- The DLQ is a regular queue channel — it is polled and read the same way as any other queue;
autoAckMessages(true)avoids leaving an un-acked message in the DLQ.
Related
Was this page helpful?