[← Consuming](05-consuming.md) · [Module README](../README.md) · [Testing →](07-testing.md) # 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`](../src/test/java/com/ankurm/kafkabasics/EffectiveConfigTest.java): ``` === 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](https://ankurm.com/spring-kafka-4-1-error-handling-dlt-retry-topics/) 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: ```java 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 ```yaml spring: kafka: listener: ack-mode: MANUAL ``` ```java @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`](../src/test/java/com/ankurm/kafkabasics/ManualAckTest.java) 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 →](07-testing.md)