Files
spring-messaging-demo/rabbitmq/docs/05-acknowledgement.md
2026-08-29 10:30:21 +05:30

3.5 KiB

← Dead-lettering · Module README · Changing your mind →

5. Acknowledgement

spring:
  rabbitmq:
    listener:
      simple:
        acknowledge-mode: manual
        prefetch: 1

Three modes, and the default is not the one you want for work that matters.

Mode Behaviour
auto (default) the container acks after the listener returns, nacks on exception
manual your code calls basicAck / basicNack; nothing else does
none the broker forgets the message the moment it is delivered

(Boot publishes no default for acknowledge-mode either; the field initialiser in AbstractMessageListenerContainer is AcknowledgeMode.AUTO.)

none is genuinely fire-and-forget: no redelivery, ever, and no flow control either, because prefetch is meaningless without acknowledgement. It is the right choice for metrics and the wrong choice for everything else.

auto is a reasonable default and its name is misleading: it does not mean "auto-ack" in the AMQP sense (that is none). It means the container decides, and it decides by whether your listener threw.

manual is what you want when the acknowledgement must happen after some other durable side effect — a database commit, a downstream call. In manual mode the listener takes a Channel and a delivery tag:

@RabbitListener(queues = "orders.work")
void onOrder(OrderMessage message, Channel channel,
        @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
    try {
        process(message);
        channel.basicAck(tag, false);
    }
    catch (TransientFailure ex) {
        channel.basicNack(tag, false, !alreadyRedelivered);
    }
    catch (PermanentFailure ex) {
        channel.basicNack(tag, false, false);   // dead-letter it
    }
}

The two catch blocks are the point. A single catch (Exception) that nacks with requeue=true is the loop from chapter 4; a single one that nacks with requeue=false dead-letters transient failures that would have succeeded on a retry. Deciding which kind of failure you had is work you cannot delegate to the container.

prefetch is the flow-control knob

prefetch is how many un-acknowledged messages the broker will send a consumer. Spring Boot sets no default, so AbstractMessageListenerContainer.DEFAULT_PREFETCH_COUNT applies, and that constant is 250 — throughput-oriented: a slow consumer holds 250 messages that no other consumer can take, so one stuck worker parks 250 messages behind it.

Set it to 1 when messages are expensive and unevenly sized — it gives you round-robin distribution across consumers. Leave it high when messages are small and uniform and you want throughput. This is the single most effective RabbitMQ tuning parameter and it is almost never touched.

The forgotten ack

Not calling basicAck does not lose the message and does not throw. The message stays un-acknowledged, counting against prefetch, until the channel closes. With prefetch: 1 the consumer stops after exactly one message; with the default it stops after 250. Both look like the consumer "just stopped", hours apart in wall-clock time depending on traffic.

rabbitmqctl list_queues name messages_ready messages_unacknowledged is the command that distinguishes them. A large messages_unacknowledged with a live consumer means somebody forgot an ack.

Changing your mind →