A persisted checkpoint is unsafe when the query result or source file changes under the same manifest ID.
Spring Batch source digest: reject changed rows before a restart
Bind bytes to identity
The file-backed fixture hashes its ordered source ID and unit pairs when it first sees a manifest. On a later launch it recomputes the digest before calling the launcher. The test changes ID 4 from 44 to 82 after a failed run. The relaunch is rejected as changed input, leaves the two committed target rows untouched and creates no second Batch execution.
Use a real snapshot in production
Hashing a mutable table at launch is a gate, not an immutable source. Another writer could change it after the digest check and before the Batch reader consumes it. Pin a file version, a database snapshot or a versioned staging table. Add the manifest identity, source version, count and digest to a reviewable record; the input guide explains why a pathname alone is insufficient.
Make replay conflicts explicit
The H2 writer keys output by source ID and can replace a previously written row. The digest prevents this tested changed-source relaunch, but it does not validate every record's provenance or an external side effect. A correction should get a new manifest and a documented relationship to the failed one. Reconciliation must still account for accepted, rejected and unprocessed IDs.
Checked excerpt
String observedHash = sourceHash(jdbc);
String storedHash = jdbc.query(
"SELECT source_hash FROM receipt_manifest WHERE manifest_id = ?",
result -> result.next() ? result.getString(1) : null, manifestId);
if (storedHash != null && !storedHash.equals(observedHash)) {
throw new IllegalStateException("source changed for manifest " + manifestId);
}Cost and verification
Digesting n source rows costs O(n) reading time and a small fixed hash state. Pinning an immutable snapshot adds storage and retention work.
Common Mistakes
- Do not treat a digest computed before the read as isolation from concurrent mutations.
- Do not reuse the old manifest ID for corrected input.
- Do not let an upsert hide changed source values.
Read next
Spring Batch restart input: pin the manifest before resuming a cursor, Spring Batch writers: use a stable source key when a chunk is replayed, Spring Batch killed worker: fence it, recover STARTED, then restart, Spring Batch reconciliation: report accepted and rejected receipt IDs.
