A restart can encounter a previously committed item; a stable business key lets the destination detect or reconcile that replay.
Spring Batch writers: use a stable source key when a chunk is replayed
Make the replay decision visible
The H2 fixture keys receipt_import by source ID. A plain INSERT exposed a duplicate-key failure on restart even though the first chunk had committed. The checked writer uses H2 MERGE keyed by ID. After restart, the table has four rows: ID 1 retains seven units and ID 4 has 28. This MERGE syntax is H2-specific. A production ledger should also reject a reused source ID whose payload changed, rather than silently overwriting it.
Keep writer state separate from metadata
The failed execution's saved reader context reports two reads, yet the local restart can revisit earlier rows. Even with another reader implementation, a crash between business and repository commits can make replay possible. Keep the sink key stable and the operation idempotent. An external broker send is a separate side effect; the outbox records intent inside the business transaction.
Checked excerpt
jdbc.update("MERGE INTO receipt_import (id, units) KEY (id) VALUES (?, ?)",
receipt.id(), receipt.units());Cost and verification
The unique key adds indexed lookup work per row. An upsert may write an existing row again; compare content or version before accepting a changed replay. The isolated source-kit project checks the stated local behavior.
Common Mistakes
- Do not silently overwrite a changed source record under the same ID.
- Do not confuse a saved reader count with exactly-once output.
- Do not copy H2 MERGE syntax into another database without checking semantics.
Read next
Spring Batch chunks: a later failure does not erase an earlier commit, Spring Batch job identity: a manifest ID defines restart versus a new run, Spring transactional outbox: commit a receipt and event row together, Spring POST idempotency keys: bind replay to tenant and command.
Related Spring path
Continue with Spring Batch reconciliation: report accepted and rejected receipt IDs.
