Concurrent item processing changes thread ownership; a processor should not assume the step transaction is active on its worker thread.
Spring Batch multithreaded step: keep processor work independent of the chunk transaction
Separate computation from effects
A settlement processor normalizes receipt references and computes minor-unit totals. In a multithreaded step, several processor calls may run at once. Keep that code stateless and pure so ordering and thread identity do not affect the output. Spring transactions are thread-bound; work moved to a processor thread should not rely on the main chunk transaction. A database write from that worker can commit independently and survive a later chunk rollback. Committed chunk boundaries explain the recovery consequence.
Bound the parallelism
Measure the serial reader, processor and writer separately before increasing threads. A slow shared reader or writer can leave processors idle, while a database pool smaller than the requested concurrency can turn parallelism into connection waits. Size the executor, connection pool and downstream admission together. Bounded executor rejection matters if a scheduler launches many jobs at once.
Verify repeatability
Run the same immutable source with one worker and then with several. Compare output by stable receipt key rather than row order. Inject an exception in one processor and assert no separately committed business effect remains from that item. If the processor must call a remote service, move it to an explicit, replay-safe stage with an idempotency key rather than hiding it inside a pure mapping step.
Implementation contract
final class NormalizeReceipt implements ItemProcessor<RawReceipt, NormalizedReceipt> {
@Override public NormalizedReceipt process(RawReceipt receipt) {
String reference = receipt.reference().strip();
long amountMinor = Math.multiplyExact(receipt.unitPriceMinor(), receipt.units());
return new NormalizedReceipt(reference, amountMinor);
}
}Cost and verification
Parallel processors help only when processor CPU or latency dominates. More threads consume memory and connections; the serial reader and writer can remain the throughput limit.
Common Mistakes
- Do not put mutable counters in an unsynchronized shared processor.
- Do not assume a worker thread inherits the chunk transaction.
- Do not add threads before measuring reader and writer throughput.
Read next
Spring Batch chunks: a later failure does not erase an earlier commit, Spring Batch partitioner: assign disjoint receipt ranges, Spring task executors: reject work when every slot is occupied, Spring Batch parallel flows: make dependency order explicit, Spring imperative transaction: a worker thread does not inherit it.
