# How to Handle Message Acknowledgment (Ack/Nack) in Kourier

> Learn to handle message acknowledgment ack/nack in Kourier using AMQP 0-9-1 models basicAck, basicNack, and basicReject with delivery tags from Delivery message.

- Repository: [Guimauve Digital/kourier](https://github.com/guimauvedigital/kourier)
- Tags: how-to-guide
- Published: 2026-03-02

---

**Kourier exposes RabbitMQ's AMQP 0-9-1 acknowledgment model through the `AMQPChannel` interface, allowing consumers to explicitly acknowledge (`basicAck`), negatively acknowledge (`basicNack`), or reject (`basicReject`) messages using delivery tags obtained from `Delivery.message`.**

The `guimauvedigital/kourier` library provides Kotlin multiplatform support for AMQP brokers, implementing the full message acknowledgment lifecycle required for reliable message processing. Understanding how to properly acknowledge or reject deliveries is critical for building fault-tolerant consumers that prevent message loss while handling processing failures gracefully.

## Understanding the AMQP Acknowledgment Model in Kourier

Kourier follows the RabbitMQ AMQP 0-9-1 specification where every message delivered to a consumer must be explicitly acknowledged. The library exposes this behavior through the `AMQPChannel` interface and its default implementation `DefaultAMQPChannel`.

When consuming messages, the `noAck` parameter in `basicConsume` determines the acknowledgment mode:

- **`noAck = true`**: Automatic acknowledgment. The broker treats the message as acknowledged immediately upon delivery.
- **`noAck = false`**: Manual acknowledgment. The client must call `basicAck`, `basicNack`, or `basicReject` using the delivery tag.

The interface definition in **[AMQPChannel.kt lines 162-176]** declares `basicConsume` with `noAck: Boolean = false`, defaulting to manual acknowledgment for safety.

## Core Acknowledgment Methods

### basicAck: Positive Acknowledgment

Use `basicAck` to signal successful message processing. The method signature in **[AMQPChannel.kt lines 194-206]** accepts:

- `deliveryTag`: The broker-assigned identifier from `Delivery.message.deliveryTag`
- `multiple`: When `true`, acknowledges all messages up to and including the supplied tag (batch acknowledgment)

The implementation in **[DefaultAMQPChannel.kt lines 317-328]** constructs a `Frame.Method.Basic.Ack` frame and writes it to the channel.

### basicNack: Negative Acknowledgment with Options

Use `basicNack` when processing fails but you want control over requeue behavior. Declared in **[AMQPChannel.kt lines 207-222]**, it accepts:

- `deliveryTag`: The message identifier
- `multiple`: Batch operation flag
- `requeue`: `true` returns the message to the queue; `false` discards it (potentially to a dead-letter exchange)

The implementation in **[DefaultAMQPChannel.kt lines 329-343]** sends `Frame.Method.Basic.Nack`.

### basicReject: Single Message Rejection

`basicReject` provides a convenience wrapper for rejecting a single message. Defined in **[AMQPChannel.kt lines 223-238]`, it is equivalent to `basicNack` with `multiple = false`.

The implementation in **[DefaultAMQPChannel.kt lines 362-380]** handles the rejection logic.

### basicRecover: Requeue Unacknowledged Messages

Use `basicRecover` after a consumer crash to requeue all unacknowledged messages. The method in **[AMQPChannel.kt lines 239-250]** accepts a `requeue` parameter:

- `requeue = true`: Messages are requeued and may be redelivered to any consumer
- `requeue = false`: Messages are redelivered to the original consumer

Implemented in **[DefaultAMQPChannel.kt lines 383-393]`.

## Configuring Consumer Acknowledgment Behavior

### Manual Acknowledgment Mode

For reliable processing, always consume with `noAck = false` (the default). This ensures messages remain in the broker until explicitly acknowledged:

```kotlin
val consumer = channel.basicConsume(
    queue = "task_queue",
    noAck = false,  // Manual acknowledgment required
    consumerTag = "worker-1"
)

```

This pattern prevents message loss during consumer failures but requires careful exception handling to avoid unacknowledged message buildup.

### Automatic Acknowledgment Mode

Use `noAck = true` only for fire-and-forget scenarios where message loss is acceptable:

```kotlin
val consumer = channel.basicConsume(
    queue = "metrics_queue",
    noAck = true  // Auto-acknowledged by broker
)

```

In this mode, the broker removes the message from the queue immediately upon delivery, regardless of processing success or failure.

## Practical Usage Patterns

### Work Queue Pattern with Manual Ack

The standard work queue implementation demonstrates proper success and failure handling:

```kotlin
suspend fun processTasks(channel: AMQPChannel) {
    val consumer = channel.basicConsume("task_queue", noAck = false)
    
    for (delivery in consumer) {
        val body = delivery.message.body.decodeToString()
        try {
            // Process the task
            executeTask(body)
            // Success: acknowledge the message
            channel.basicAck(delivery.message, multiple = false)
        } catch (e: Exception) {
            // Failure: requeue for retry
            channel.basicNack(delivery.message, multiple = false, requeue = true)
        }
    }
}

```

See the complete example in **[Work Queues tutorial lines 56-70]**.

### Dead Lettering with Reject

When message content is invalid and should not be retried:

```kotlin
if (!validateMessage(payload)) {
    // Reject permanently (send to dead letter exchange)
    channel.basicReject(
        deliveryTag = delivery.message.deliveryTag,
        requeue = false
    )
    continue
}

```

This pattern routes invalid messages to a dead-letter queue for inspection rather than infinite retry loops.

### Batch Acknowledgment for Performance

For high-throughput scenarios, accumulate delivery tags and acknowledge in batches:

```kotlin
var lastTag: ULong = 0u
var messageCount = 0

for (delivery in consumer) {
    processMessage(delivery)
    lastTag = delivery.message.deliveryTag
    messageCount++
    
    // Acknowledge every 10 messages
    if (messageCount % 10 == 0) {
        channel.basicAck(deliveryTag = lastTag, multiple = true)
    }
}

```

The `multiple = true` parameter in **[DefaultAMQPChannel.kt lines 317-328]** acknowledges all messages up to the specified tag, reducing network round-trips.

### Handling Stale Delivery Tags with RobustAMQPChannel

When using connection recovery, delivery tags from old channels become invalid. The `RobustAMQPChannel` wrapper safely handles these:

```kotlin
val robustChannel = RobustAMQPChannel(wrapped = channel)

// After reconnection, old tags are silently ignored
robustChannel.basicAck(staleTag, multiple = false)  // No exception thrown

```

This implementation in **[RobustAMQPChannel.kt lines 61-88]** prevents `PRECONDITION_FAILED` errors during recovery by filtering out stale tags before they reach the underlying channel.

## Summary

- **Manual acknowledgment** (`noAck = false`) is the default and recommended mode for reliable message processing in Kourier.
- Use **`basicAck`** to confirm successful processing, **`basicNack`** for failures with optional requeue, and **`basicReject`** as a convenience for single-message rejection.
- The **`multiple`** parameter enables batch acknowledgment, improving throughput by reducing network overhead.
- **`basicRecover`** requeues unacknowledged messages after consumer crashes, ensuring no message loss during failures.
- Wrap channels with **`RobustAMQPChannel`** to safely handle stale delivery tags during connection recovery scenarios.

## Frequently Asked Questions

### What is the difference between basicNack and basicReject in Kourier?

**`basicReject`** is a convenience method that rejects a single message and is equivalent to calling **`basicNack`** with `multiple = false`. Both methods accept a `requeue` parameter that determines whether the message returns to the queue (`true`) or is discarded/dead-lettered (`false`). Use `basicNack` when you need to reject multiple messages at once using the `multiple` parameter, and `basicReject` for single-message rejection scenarios.

### How do I prevent message loss when a Kourier consumer crashes?

Configure your consumer with `noAck = false` (manual acknowledgment) and ensure messages are only acknowledged after successful processing. If a crash occurs before acknowledgment, the broker retains the message and redelivers it to another consumer. Additionally, implement **`basicRecover(requeue = true)`** in your consumer startup logic to requeue any unacknowledged messages from previous sessions, ensuring they are not lost during consumer restarts.

### Can I acknowledge multiple messages at once in Kourier?

Yes, use the **`multiple`** parameter set to `true` when calling `basicAck` or `basicNack`. This acknowledges all messages up to and including the specified `deliveryTag` in a single network round-trip. For example, `channel.basicAck(deliveryTag = 50u, multiple = true)` acknowledges every message with delivery tags 1 through 50. This batch acknowledgment pattern significantly improves throughput in high-volume consumer scenarios by reducing network overhead.

### What happens if I try to acknowledge a message with a stale delivery tag?

In standard channels, attempting to acknowledge a message with a stale delivery tag (from a previous connection or channel) throws a `PRECONDITION_FAILED` error from the broker. To handle this safely during connection recovery, wrap your channel with **`RobustAMQPChannel`**, which intercepts acknowledgment calls and silently ignores those with stale delivery tags. This wrapper, implemented in [`RobustAMQPChannel.kt`](https://github.com/guimauvedigital/kourier/blob/main/RobustAMQPChannel.kt), prevents application crashes during network reconnection scenarios while maintaining message processing continuity.