skip to content

Explain AcknowledgeMode AUTO vs MANUAL in Spring AMQP. When do you use MANUAL and how do you correctly ack/nack a message?

level: seniorimportance: must knowfreq 70%

answer

  1. AUTO ≠ AMQP autoAck: container acks on return, nacks on throw
  2. MANUAL: inject Channel + DELIVERY_TAG header, basicAck yourself
  3. NONE = AMQP auto-ack = lose on crash
  4. forget to ack -> prefetch fills -> consumer stalls
  5. nack requeue=true poison loop -> use DLQ instead

basics

~20 s

AUTO means Spring acks the message for you after your listener returns normally, and nacks if it throws. MANUAL means you inject the Channel and delivery tag and call channel.basicAck / basicNack / basicReject yourself, giving full control over when a message is confirmed or requeued.

solid answer

~40 s

AcknowledgeMode controls who tells RabbitMQ a message is handled. AUTO (Spring AMQP's default — not the same as AMQP auto-ack) means the container acks after a successful listener return and nacks (requeue or dead-letter) on exception; it's the right choice most of the time. NONE maps to AMQP auto-ack: the broker considers messages delivered immediately, so a crash loses them — avoid unless loss is acceptable. MANUAL means you take responsibility: declare a Channel parameter, read the delivery tag from the AMQP_DELIVERY_TAG header, and call channel.basicAck(tag,false) on success or basicNack/basicReject(tag,false,requeue) on failure. Use MANUAL when acking must be tied to external work (e.g. only after a DB commit), for batch acks (multiple=true), or nuanced requeue-vs-dead-letter decisions. The danger: forget to ack and the prefetch window fills, stalling the consumer.

code

java · 22 lines
java
// Factory configured with MANUAL ack
@Bean
SimpleRabbitListenerContainerFactory manualAckFactory(ConnectionFactory cf) {
    var f = new SimpleRabbitListenerContainerFactory();
    f.setConnectionFactory(cf);
    f.setAcknowledgeMode(AcknowledgeMode.MANUAL);
    return f;
}

@RabbitListener(queues = "orders.q", containerFactory = "manualAckFactory")
public void handle(OrderCreated event,
                   Channel channel,
                   @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
    try {
        processAndCommit(event);          // e.g. write to DB, commit
        channel.basicAck(tag, false);     // confirm only after success
    } catch (TransientException e) {
        channel.basicNack(tag, false, true);   // requeue for retry
    } catch (PoisonException e) {
        channel.basicNack(tag, false, false);  // don't requeue -> dead-letter/drop
    }
}

go deeper

for a junior

Know AUTO lets Spring ack for you and MANUAL means you call basicAck yourself.

for a middle

Explain AUTO≠auto-ack, and the Channel + delivery-tag mechanics of MANUAL.

for a senior

Discuss coupling acks to DB commits, requeue vs dead-letter, and prefetch-stall failure mode.

for a principal

Design delivery guarantees end to end: idempotency, DLQ/retry topology, and when NONE's loss is acceptable.

## What 'acknowledge' means RabbitMQ keeps a delivered message pending until the consumer **acknowledges** it. On an ack the broker deletes it; on a **nack/reject** it can requeue or dead-letter it. Until acked (or the channel closes), the message counts against the consumer's prefetch window and, on channel/connection loss, is redelivered. So acks are how you get **at-least-once** processing. ## The three Spring AcknowledgeModes Spring AMQP's `AcknowledgeMode` enum (set on the container/factory) has three values: ### AUTO (Spring's default) **Misleadingly named** — this is *not* AMQP's `autoAck`. It means **the container acks for you** based on the outcome: - Listener returns normally → container sends `basicAck`. - Listener throws → container sends `basicNack`/`basicReject`; whether it requeues or dead-letters depends on `defaultRequeueRejected` and any error handler / retry. By default a thrown exception requeues once and, with retries exhausted or `defaultRequeueRejected=false`, the message is rejected (dropped or dead-lettered). This is the correct choice for most listeners: reliable, simple, exception-driven. ### MANUAL The container **never acks**; you must. You inject the `com.rabbitmq.client.Channel` and the **delivery tag**, then call the client ack methods yourself: - `channel.basicAck(deliveryTag, multiple)` — confirm (multiple=true acks all up to the tag). - `channel.basicNack(deliveryTag, multiple, requeue)` — negative ack, optionally requeue. - `channel.basicReject(deliveryTag, requeue)` — reject a single message. Use MANUAL when the ack must be coupled to *your* logic: ack only after a downstream DB commit succeeds, batch-ack many messages, or make per-message requeue-vs-dead-letter decisions. ### NONE Maps to AMQP **auto-ack=true**: the broker treats a message as acknowledged the instant it's delivered, before your code runs. Fastest, but **any crash loses in-flight messages** — only for fire-and-forget/lossy streams. ## Getting the delivery tag With `@RabbitListener` you obtain the tag from the header: ```java @Header(AmqpHeaders.DELIVERY_TAG) long tag ``` and inject the `Channel` as a parameter. The container passes the *same* channel the message was delivered on — you must ack on that channel. ## Common gotchas - **Forgetting to ack in MANUAL mode**: unacked messages fill the prefetch window; once full the broker stops delivering and the consumer appears hung. Always ack or nack on every path, including exceptions. - **basicNack requeue=true loops**: a poison message that always fails and is requeued creates an infinite redelivery loop. Route to a DLQ instead (requeue=false with a dead-letter exchange) or cap retries. - **multiple=true acks everything up to that tag** — convenient for batching but acks messages you may not have finished. - **Wrong channel**: acking on a different channel than the delivery throws a 'unknown delivery tag' / channel error. - AUTO already handles the happy path — don't reach for MANUAL unless you truly need external coupling. ## When to choose which | Need | Mode | |------|------| | Standard reliable processing | AUTO | | Ack only after DB commit / external side effect | MANUAL | | Batch acknowledgment | MANUAL (multiple=true) | | Fine-grained requeue vs dead-letter control | MANUAL | | Max throughput, loss acceptable | NONE |

  • Why is AcknowledgeMode.AUTO not the same as AMQP auto-ack?
    AMQP auto-ack (Spring's NONE) tells the broker to consider a message acked on delivery, before processing — a crash loses it. Spring's AUTO instead has the container send the ack after the listener returns successfully (and nack on exception), giving at-least-once safety.
  • In MANUAL mode a message always fails and you nack with requeue=true. What's the risk and the fix?
    Infinite redelivery loop (poison message) burning CPU. Fix: nack/reject with requeue=false and configure a dead-letter exchange/queue so failures go to a DLQ, or cap retries and route exhausted messages to the DLQ.

saying these in an interview costs you the question

  • Saying AUTO means the broker auto-acks on delivery (that's NONE/AMQP auto-ack; AUTO acks after successful processing).
  • Nacking with requeue=true on a permanently-failing message, causing an infinite loop.
  • Forgetting to ack on every code path in MANUAL mode, stalling the consumer once prefetch fills.
  • Acking on a different Channel than the one the message was delivered on.

context