Add the kafka-basics module

This commit is contained in:
2026-08-29 09:47:25 +05:30
commit 3a682e496e
28 changed files with 1403 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
[← 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)