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
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 = 2means 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 thepolicyparameter ofqueueMessage { ... }— 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.
Related
- Dead Letter Queue — task-oriented walkthrough for DLQ routing
- Ack & Reject
- Queues Reference
Was this page helpful?