If your stream processor sometimes double-counts or drops a record after a crash, you are running at-least-once or at-most-once semantics. Kafka Streams can give you exactly-once โ each input record affects the output and state exactly one time, even across failures. Here is how to turn it on and what it actually guarantees.
Enable It
Exactly-once in Kafka Streams is a single configuration flag:
Properties props = new Properties(); props.put(StreamsConfig.APPLICATION_ID_CONFIG, "orders-aggregator"); props.put(StreamsConfig.PROCESSING_GUARANTEE_CONFIG, StreamsConfig.EXACTLY_ONCE_V2); // the modern, efficient version
That one setting wires up transactional writes across your output topics and state stores.
What It Actually Guarantees
Under the hood, Kafka Streams wraps each processing cycle in a transaction: reading input offsets, updating state, and writing output all commit together, or not at all. If the app crashes mid-cycle, the transaction aborts and the whole cycle replays cleanly โ no partial writes survive.
The guarantee holds within the Kafka ecosystem: input topics โ processing โ state stores โ output topics. It does not magically extend to an external database you write to as a side effect (see below).
Keep Side Effects Idempotent
Exactly-once covers Kafka reads, state, and Kafka writes. A call to an external system inside your processor is not part of the transaction:
stream.foreach((key, value) -> externalDb.write(value)); // NOT covered
If you must write externally, make that write idempotent โ key it so a replay overwrites rather than duplicates. Otherwise a retried transaction re-runs the side effect.
Verify It
Kill the application mid-stream and restart it, then check that downstream counts match the input exactly. Watch the consumer group: offsets should only advance on committed transactions. If counts are correct after a hard crash, exactly-once is working.
Summary
Set PROCESSING_GUARANTEE_CONFIG to EXACTLY_ONCE_V2 and Kafka Streams commits input offsets, state, and output atomically per cycle โ a crash replays cleanly with no duplicates or gaps. Just remember the guarantee lives inside Kafka: keep any external side effects idempotent, then crash-test it to confirm your counts hold.