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.commitistrue. - Spring Boot does not set it. It is absent from
ConsumerFactory.getConfigurationProperties(), before the containers start and after. ConsumerFactory.isAutoCommit()returnstrueon 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.determineAutoCommitchecks whether the factory config contains the key and, when it does not, callssetProperty("enable.auto.commit", "false")on the per-containerPropertieshanded tocreateConsumer.
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
MANUALwhile auto-commit is genuinely enabled is anIllegalStateExceptionat container start. It does not happen on a stock configuration — despiteisAutoCommit()returningtrue— becausedetermineAutoCommitsets the container property tofalsebefore the check runs.ManualAckTestpins that down, because it is exactly the kind of interaction that would otherwise be a surprise in production. - An
Acknowledgmentyou forget to call stalls the partition. Not immediately — the container keeps polling untilmax.poll.recordsof 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.