Explain the container AckMode values (RECORD, BATCH, MANUAL/MANUAL_IMMEDIATE) and how each affects offset commits.
answer
- spring-kafka sets enable.auto.commit=false
- RECORD = per record, BATCH = per poll (default)
- MANUAL queues to next poll; MANUAL_IMMEDIATE commits now
- Acknowledgment.acknowledge() in manual modes
- all at-least-once; commit-after-process
basics
~20 sAckMode controls when the container commits consumer offsets. RECORD commits after each record's listener returns. BATCH commits once after the whole poll batch is processed (the default). MANUAL/MANUAL_IMMEDIATE hand control to you via an Acknowledgment.acknowledge() call.
solid answer
~40 sSpring-kafka disables Kafka's auto-commit (enable.auto.commit=false) and commits offsets itself per the container's AckMode. RECORD commits the offset after each record's listener method returns successfully. BATCH (the default) commits all offsets from a poll once the whole batch finishes — fewer commits, but on failure the whole batch may be redelivered. TIME and COUNT commit on elapsed time or record count thresholds; COUNT_TIME is either-or. MANUAL and MANUAL_IMMEDIATE require an Acknowledgment parameter: your code calls ack.acknowledge(); MANUAL queues the commit to happen at the next poll boundary (like BATCH timing), while MANUAL_IMMEDIATE commits synchronously right away. Choice trades commit overhead against redelivery/duplicate risk — offsets are commit-after-process, so at-least-once is the baseline and crashes between process and commit cause reprocessing.
code
java · 7 linesfactory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL_IMMEDIATE);
@KafkaListener(topics = "orders", groupId = "billing")
public void handle(Order order, Acknowledgment ack) {
repository.save(order); // side effect first
ack.acknowledge(); // commit offset only after success
}go deeper
Know AckMode decides when offsets commit and that MANUAL means you call acknowledge().
Distinguish RECORD/BATCH/MANUAL and the duplicate-vs-overhead tradeoff.
Explain MANUAL vs MANUAL_IMMEDIATE timing, TIME/COUNT modes, and at-least-once baseline.
Compose AckMode with transactions/error handlers/DLT for end-to-end delivery guarantees and ops tuning.
**Why offsets matter.** A Kafka consumer tracks its position per partition as an *offset*. On restart or rebalance, it resumes from the last *committed* offset. Committing too early risks losing records (committed but not processed → at-most-once); committing after processing risks duplicates (processed but crashed before commit → at-least-once). Spring-kafka turns **off** the client's `enable.auto.commit` and manages commits via the container's `AckMode` so commits align with successful processing. **The AckMode values (ContainerProperties.AckMode):** - **RECORD** — commit the offset after *each* record's listener returns normally. Lowest redelivery window (only the in-flight record can be reprocessed) but the most commit traffic. - **BATCH** — *(default)* commit the offsets for the entire batch returned by one `poll()` after all its records are processed. Fewer commits; but if record 50 of 100 fails (and the error handler reprocesses), records may be redelivered. - **TIME** — commit when `ackTime` has elapsed since the last commit (pending acks are flushed). - **COUNT** — commit after `ackCount` records have been processed. - **COUNT_TIME** — commit when *either* the count *or* the time threshold is reached. - **MANUAL** — the listener must take an `Acknowledgment` argument and call `acknowledge()`; the actual commit is *queued* and performed at the next poll (BATCH-like timing). Gives you control over *which* records are acked while batching the network commit. - **MANUAL_IMMEDIATE** — same manual control, but `acknowledge()` triggers the commit *immediately* (synchronously on the consumer thread) rather than deferring to the next poll. **Manual mode mechanics.** With `factory.getContainerProperties().setAckMode(AckMode.MANUAL_IMMEDIATE)`, the method signature gains `Acknowledgment ack`; calling `ack.acknowledge()` commits up to that record. Forgetting to call it means offsets never advance and records are redelivered after restart. Manual modes are common when you commit only after an external side effect succeeds (e.g. DB write), or to nack/seek for retry. **Interaction with errors and batch listeners.** With a `DefaultErrorHandler`, a failed record can be retried and then sent to a dead-letter topic; offsets advance appropriately so the batch isn't infinitely replayed. For **batch listeners** (`List<...>` per invocation), MANUAL acks the whole list, and `BatchAcknowledgingMessageListener`/`acknowledge(index)` (newer APIs) allow partial acks. **Delivery semantics.** All these modes are fundamentally **at-least-once** because commit happens *after* processing. To approach exactly-once for produce-after-consume flows, you combine transactions (`transactionIdPrefix` on the producer factory + `isolation.level=read_committed` on consumers) so offsets are committed *within* the producing transaction, rather than relying on AckMode alone. **Edge cases.** A rebalance between process and commit causes the new owner to reprocess. MANUAL_IMMEDIATE adds a synchronous commit per ack, increasing latency under high throughput. BATCH minimizes commit calls but widens the duplicate window on crash.
- What is the practical difference between MANUAL and MANUAL_IMMEDIATE?Both require calling ack.acknowledge(). MANUAL defers the actual commit to the next poll boundary (batching the network call, BATCH-like timing); MANUAL_IMMEDIATE commits synchronously the moment you call acknowledge().
- Do any of these AckModes give exactly-once delivery?No — they are all at-least-once because offsets are committed after processing. Exactly-once for consume-process-produce needs Kafka transactions (transactionIdPrefix + read_committed) so offset commits join the producing transaction.
saying these in an interview costs you the question
- Saying spring-kafka relies on enable.auto.commit (it disables it by default)
- Claiming BATCH is safer than RECORD against duplicates (it widens the window)
- Thinking MANUAL alone gives exactly-once
- Forgetting that not calling acknowledge() in MANUAL mode stalls offset progress