Files
spring-messaging-demo/kafka-basics/docs/06-acknowledgement.md

3.5 KiB

← Consuming · Module README · Testing →

6. Acknowledgement, and a property that is not where you look for it

The container's default AckMode is BATCH: commit the offsets of the whole poll() batch after the listener has returned for every record in it. Confirmed at runtime by EffectiveConfigTest:

=== listener container ===
ackMode                                        BATCH
groupId                                        orders-basic

That gives at-least-once delivery. A crash after processing and before the commit redelivers the batch, which is why your listener must be idempotent — and why the error-handling article is a necessary sequel rather than an optional one.

Where enable.auto.commit actually lives

This one is worth the detour, because every debugging session about commits starts in the wrong place.

  • The kafka-clients default for enable.auto.commit is true.
  • Spring Boot does not set it. It is absent from ConsumerFactory.getConfigurationProperties(), before the containers start and after.
  • ConsumerFactory.isAutoCommit() returns true on a stock Boot 4.1 application, because it reads that same absent key and falls back to the client default.
  • And yet no consumer auto-commits, because ListenerConsumer.determineAutoCommit checks whether the factory config contains the key and, when it does not, calls setProperty("enable.auto.commit", "false") on the per-container Properties handed to createConsumer.

So the shared factory never learns, its public accessor answers the opposite of the truth, and the real value lives in an override map you cannot reach from application code. All four of those statements are asserted in the test, including the counter-intuitive one:

assertThat(this.consumerFactory.isAutoCommit()).isTrue();

If you ever need to know whether a running consumer auto-commits, read the consumer's own metrics or its startup log line, not the factory.

Manual acknowledgement

spring:
  kafka:
    listener:
      ack-mode: MANUAL
@KafkaListener(topics = "orders")
void onOrder(OrderEvent event, Acknowledgment acknowledgment) {
    process(event);
    acknowledgment.acknowledge();
}

Nothing commits until acknowledge() runs, which is what you want when the work must be durable before the offset moves. Two things to know:

  • Asking for MANUAL while auto-commit is genuinely enabled is an IllegalStateException at container start. It does not happen on a stock configuration — despite isAutoCommit() returning true — because determineAutoCommit sets the container property to false before the check runs. ManualAckTest pins that down, because it is exactly the kind of interaction that would otherwise be a surprise in production.
  • An Acknowledgment you forget to call stalls the partition. Not immediately — the container keeps polling until max.poll.records of un-acknowledged records accumulate. So the symptom is a consumer that works for a while and then stops, which reads like a broker problem.

MANUAL_IMMEDIATE commits synchronously on the consumer thread instead of at the end of the batch. It is slower and it is the right choice when redelivery is genuinely expensive.

Testing →