KubeMQ
Client SDKsKotlinHow-to guidesQueues

Dead Letter Policy

Field-level reference for QueueMessagePolicy's maxReceiveCount and maxReceiveQueue

Which to use

This page is the field-level reference for QueueMessagePolicy. For the end-to-end task — sending a message, rejecting it until it exhausts retries, and consuming from the resulting DLQ — see Dead Letter Queue.

Overview

QueueMessagePolicy carries two fields — maxReceiveCount and maxReceiveQueue — that together define a message's dead-letter routing. The policy is attached to a message at send time and travels with it; the producer, not the consumer, decides the retry ceiling.

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.

Prerequisites

  • KubeMQ server running on localhost:50000
  • Kotlin SDK installed (implementation("io.kubemq.sdk:kubemq-sdk-kotlin:1.0.1"))

Code

DeadLetterPolicyExample.kt
package io.kubemq.sdk.examples.queuesstream

import io.kubemq.sdk.client.KubeMQClient
import io.kubemq.sdk.queues.QueueMessagePolicy
import io.kubemq.sdk.queues.queueMessage
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

private const val ADDRESS = "localhost:50000"
private const val CLIENT_ID = "kotlin-queues-dead-letter-policy"
private const val CHANNEL = "kotlin-queues.dead-letter-policy"
private const val DLQ = "kotlin-queues.dead-letter-policy-dlq"

fun main() = runBlocking {
    val client = KubeMQClient.queues {
        address = ADDRESS
        clientId = CLIENT_ID
    }

    client.use {
        try {
            client.createQueuesChannel(CHANNEL)
            client.createQueuesChannel(DLQ)

            // Send message with DLQ policy (moves to DLQ after 2 rejections)
            client.sendQueuesMessage(queueMessage {
                channel = CHANNEL
                body = "Poison message".toByteArray()
                policy = QueueMessagePolicy(
                    maxReceiveCount = 2,
                    maxReceiveQueue = DLQ,
                )
            })
            println("Sent message with DLQ policy (max 2 attempts).\n")

            // Reject the message; after max attempts it should go to DLQ
            for (attempt in 1..3) {
                val resp = client.receiveQueuesMessages {
                    channel = CHANNEL
                    maxItems = 1
                    waitTimeoutMs = 2000
                    autoAck = false
                }
                if (resp.messages.isNotEmpty()) {
                    println("Attempt $attempt: Rejecting...")
                    resp.messages.first().reject()
                } else {
                    println("Attempt $attempt: No message (moved to DLQ).")
                    break
                }
                delay(500)
            }

            // Read the message from the dead letter queue
            val dlqResp = client.receiveQueuesMessages {
                channel = DLQ
                maxItems = 1
                waitTimeoutMs = 2000
                autoAck = true
            }
            if (dlqResp.messages.isNotEmpty()) {
                println("\nDLQ message: ${String(dlqResp.messages.first().body)}")
            }
        } finally {
            try { client.deleteQueuesChannel(CHANNEL) } catch (_: Exception) {}
            try { client.deleteQueuesChannel(DLQ) } catch (_: Exception) {}
        }
        println("Done.")
    }
}

Field reference

  • maxReceiveCount (Int) — the number of failed receives allowed before the broker reroutes the message. maxReceiveCount = 2 means the message survives 2 rejections; the next failed receive triggers the move.
  • maxReceiveQueue (String) — the destination channel name for diverted messages. Must be a valid, pre-existing (or auto-creatable) channel name — a typo silently creates an unrelated channel instead of failing loudly.
  • Both fields live on QueueMessagePolicy, which is set once per message via the policy parameter of queueMessage { ... } — there is no way to change it after the message is queued.

For the walkthrough of sending, rejecting, and consuming from the resulting DLQ, see Dead Letter Queue.

Was this page helpful?

On this page