Read time: ~

Consumer Acknowledgements and Prefetch

AUTO vs MANUAL acknowledgement, ack/nack/reject and requeue semantics, prefetch and concurrency, and the throughput vs safety trade-off.

Prerequisite:First Producer and ConsumerYou’ll need: The working producer and consumer from the previous module.

Acknowledgements decide when the broker is allowed to forget a message, and prefetch decides how many in-flight messages a consumer holds. Together they control the safety and throughput of every consumer you write. This module is the canonical reference for both.


What you’ll be able to do after this module

  • Explain AUTO vs MANUAL acknowledgement and when to use each.
  • Use ack, nack, and reject with the correct requeue behavior.
  • Set prefetch and concurrency to balance throughput against overload.
  • Avoid the common pitfall of infinite requeue loops.

1. What an acknowledgement is

An acknowledgement is the consumer telling the broker it is done with a message. Until the broker receives an ack, it keeps the message and will redeliver it if the consumer disconnects. This is what makes delivery at least once, as introduced in Core Concepts.

sequenceDiagram
    participant Broker as RabbitMQ
    participant Consumer

    Broker->>Consumer: deliver message (tag 42)
    alt processed successfully
        Consumer->>Broker: basicAck(42)
        Note over Broker: message removed
    else processing failed
        Consumer->>Broker: basicNack(42, requeue=?)
        Note over Broker: requeue or dead-letter
    end

2. AUTO vs MANUAL acknowledgement mode

Spring AMQP offers three acknowledge modes, set with spring.rabbitmq.listener.simple.acknowledge-mode.

ModeBehaviorUse when
AUTO (default)Spring acks after the listener returns normally, and nacks if it throws.Most cases. Ties the ack to your method outcome.
MANUALYou call ack or nack yourself on an injected Channel.You need fine control, such as batching acks or acking only after an external commit.
NONEThe broker considers a message acked the moment it is delivered.Rarely. Only when message loss is acceptable.

Caution:AcknowledgeMode.NONE means a crash mid-processing loses the message. Do not use it for work that matters.

In AUTO mode you write plain business logic. Returning normally acks; throwing triggers a nack.

@RabbitListener(queues = "inventory.queue")
public void onOrderCreated(OrderCreated event) {
    inventoryService.reserve(event); // throwing here nacks the message
}

MANUAL mode

Inject the Channel and delivery tag, then ack or nack explicitly.

@RabbitListener(queues = "inventory.queue")
public void onOrderCreated(OrderCreated event, Channel channel,
                           @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
    try {
        inventoryService.reserve(event);
        channel.basicAck(tag, false);
    } catch (Exception e) {
        channel.basicNack(tag, false, false); // do not requeue; dead-letter instead
    }
}

3. ack, nack, reject, and requeue

OperationMeaning
basicAck(tag, multiple)Confirm one message, or all up to this tag when multiple is true.
basicNack(tag, multiple, requeue)Reject one or many; requeue decides whether they return to the queue.
basicReject(tag, requeue)Reject exactly one message.

The critical parameter is requeue:

  • requeue = true puts the message back on the queue for another attempt. Useful for transient failures.
  • requeue = false discards the message, or routes it to a dead-letter exchange if one is configured.

Caution: Requeuing on a deterministic failure creates an infinite loop: the message fails, requeues, and fails again forever, burning CPU. For errors that will always fail, do not requeue. Send the message to a dead-letter exchange instead, covered in the Reliability section. Structured retry with backoff is covered in the Retry and Error Handling module.


4. Prefetch (QoS)

Prefetch is the maximum number of unacknowledged messages the broker will deliver to a single consumer before waiting for acks. It is the main throughput and fairness control.

flowchart LR
    Q["queue"]
    subgraph c1 [Consumer 1]
        direction TB
        w1["prefetch = 2<br/>2 in flight"]
    end
    subgraph c2 [Consumer 2]
        direction TB
        w2["prefetch = 2<br/>2 in flight"]
    end
    Q --> c1
    Q --> c2

Set it in application.yml:

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 20

Tuning guidance:

  • Prefetch too high: one consumer grabs a large backlog, memory grows, and work is distributed unevenly across consumers.
  • Prefetch too low (such as 1): strict fairness and low memory, but throughput suffers because the consumer waits for a round trip after each message.
  • Start around 10 to 50 for fast handlers, and lower it for slow or memory-heavy handlers. Match prefetch to how much work a consumer can safely hold at once.

5. Concurrency

Prefetch controls in-flight messages per consumer. Concurrency controls how many consumer threads process the queue in parallel.

spring:
  rabbitmq:
    listener:
      simple:
        concurrency: 3       # minimum consumer threads
        max-concurrency: 10  # scale up to this under load
        prefetch: 20

Effective in-flight work is roughly concurrency x prefetch. Increase concurrency to use more cores and drain backlogs faster. Watch that downstream systems (a database, an external API) can handle the added parallelism.

Note: Ordering is only preserved within a single consumer thread. Raising concurrency processes messages in parallel, so do not rely on strict global order. Ordering trade-offs are covered in the Idempotency and Duplicates module.


6. Throughput vs safety

The two settings pull in opposite directions:

GoalAck modePrefetch / concurrency
Maximum safetyAUTO or MANUAL, ack after successLower prefetch, modest concurrency
Maximum throughputAUTOHigher prefetch and concurrency

The sound default for a Spring Boot service handling important work: AUTO ack, prefetch around 10 to 50, concurrency matched to your cores and downstream capacity, and a dead-letter exchange for messages that cannot be processed.


Checkpoint

You should now be able to:

  • Choose AUTO or MANUAL acknowledgement for a listener.
  • Use ack, nack, and reject with the right requeue flag.
  • Explain why requeuing a deterministic failure loops forever.
  • Tune prefetch and concurrency for throughput without overloading consumers.