# Dead Letter Policy (/sdks/java/how-to/queues/dead-letter-policy)



<Callout type="info" title="Which to use">
  For the task-oriented how-to, see [Dead Letter Queue](./dead-letter-queue). This page focuses on the `attemptsBeforeDeadLetterQueue`/`deadLetterQueue` policy option itself — its evaluation point and interaction with redelivery.
</Callout>

## Overview [#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 [#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](/sdks/java))

## Code [#code]

```java title="DeadLetterPolicyExample.java"
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 [#how-it-works]

* `attemptsBeforeDeadLetterQueue(2)` on the `QueueMessage` sets a per-message retry cap; after two `reject()` calls the broker moves the message to the channel in `deadLetterQueue(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 [#related]

* [Pattern overview](/learn/queues/getting-started)
* [Java SDK Reference](/sdks/java/reference/queues)
* [Send & Receive](/sdks/java/tutorials/send-receive)
* [Ack All](/sdks/java/how-to/queues/ack-all)
