Add spring-batch: jobs, steps, chunk processing and restartability on Boot 4.1

Companion code for "Spring Batch on Boot 4.1: Jobs, Steps, Chunk Processing and
Restartability". A productImportJob configured three ways by profile against a
poisoned CSV row, run as real java -jar processes (not just JUnit) so the
restart story is genuine: a chunk fails and rolls back, the process exits, a
brand-new JVM against the same file-based H2 database resumes at the exact
next unread row (READ_COUNT 20, not 60) and completes.

Findings the build pins:
- StepBuilder.chunk(int, PlatformTransactionManager) still compiles in Batch
  6.0.5 but returns the legacy SimpleStepBuilder; chunk(int) returns the new
  ChunkOrientedStepBuilder, and only the latter is used here.
- Two different ExecutionContext classes now exist in two different packages
  (infrastructure.item vs core.repository.persistence) with different shapes.
- spring-boot-starter-batch alone gives a resourceless JobRepository that
  forgets every JobInstance the moment the JVM exits; spring-boot-starter-
  batch-jdbc is what makes the restart demo possible at all, demonstrated by
  excluding BatchJdbcAutoConfiguration and watching a "restart" collide with
  the previous run's own data instead of resuming it.
- A migration-guide summary claiming CommandLineJobRunner was removed in 6.0
  is wrong -- javap against the real jar shows @Deprecated(forRemoval=true),
  not removed.
- RepeatStatus moved from core.repeat to infrastructure.repeat, caught by the
  compiler rather than by reading docs.

11 documentation chapters, 10 captured transcripts (unit tests, javap output,
and real two-JVM scenario runs), all regenerated by scripts/run-all.sh.

Fixed after push: three dead docs.spring.io links in the doc chapters
(readersAndWriters/* and chunk-oriented-processing/*.html paths moved when
Spring Batch 6 reorganized its reference docs; corrected to the current
readers-and-writers/*, processor.html and chunk-oriented-processing.html
paths, verified 200 via curl before committing).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019DXsJ1zpikbA1MQJN6RqFA
This commit is contained in:
Claude
2026-09-13 06:37:46 +00:00
parent a9867c0423
commit b81af72bc3
40 changed files with 2001 additions and 0 deletions
@@ -0,0 +1,47 @@
# 11. Production checklist
[&larr; Previous](10-resourceless-vs-jdbc.md) | [README](../README.md)
A short list, aimed at the gap between "this demo works" and "this is safe to point at a real
feed." Each item links back to the chapter that explains the why.
- **Confirm the job repository is actually persistent** &mdash; query for `BATCH_JOB_INSTANCE`
after startup, don't assume from the dependency list. [Chapter 10](10-resourceless-vs-jdbc.md).
- **Pick chunk size from measurement, not habit.** Smaller costs more round trips; larger costs
more rework per failure. [Chapter 3](03-chunk-oriented-processing.md).
- **Decide skip vs. fail-and-restart per failure type, not per job.** A step can have a
`skipLimit` for genuinely expected bad rows and still fail hard past that limit &mdash; the two
are not mutually exclusive. [Chapter 8](08-skip-vs-restart.md).
- **Use `chunk(int).transactionManager(tx)`, not `chunk(int, tx)`,** unless you specifically need
the legacy `SimpleStepBuilder` for something the new model does not yet cover.
[Chapter 2](02-anatomy-of-a-job.md).
- **Do not use `beanMapped()` against a Java record** without checking your exact Spring
Framework version's record support; prefer `itemPreparedStatementSetter` when in doubt.
[Chapter 6](06-jdbc-writer-and-records.md).
- **Give every step you want to always re-run on restart `allowStartIfComplete(true)`
explicitly** &mdash; a reporting or notification step that silently gets skipped after a
restart is a surprising, hard-to-notice gap. [Chapter 8](08-skip-vs-restart.md).
- **A custom `ItemReader` that does not implement `ItemStream` restarts from scratch every time**,
which is fine for an idempotent reader and a bug for anything else &mdash; check this
deliberately rather than by finding out during an incident. [Chapter 7](07-restartability.md).
- **`spring.batch.job.enabled=false` if you launch jobs yourself** through `JobOperator`, so the
auto-configured runner does not also try. [Chapter 5](05-launching-and-jobparameters.md).
- **Delete or secure any diagnostic endpoint you add while building this out.** This module has
none, but the pattern (an endpoint dumping live `JobExplorer` state) is common enough to call
out: it is invaluable while developing and a liability left in production.
## Should you even build a custom batch job for this?
Sometimes the honest answer is no. If the volume is small enough to fit in a single request-response
cycle and does not need to survive a crash mid-way, a scheduled `@Component` method with its own
try/catch is less machinery than a full Job/Step/JobRepository setup, and easier for the next
person to read. Spring Batch earns its complexity when you actually need the things this article
demonstrates &mdash; durable progress tracking, transactional chunking, a real restart story
&mdash; not by default just because the word "batch" is in the requirements.
## Further reading
- [Spring Batch reference documentation](https://docs.spring.io/spring-batch/reference/) (`rel="nofollow"`)
- [What's new in Spring Batch 6](https://docs.spring.io/spring-batch/reference/whatsnew.html) (`rel="nofollow"`)
- [Spring Batch 6.0 Migration Guide](https://github.com/spring-projects/spring-batch/wiki/Spring-Batch-6.0-Migration-Guide) (`rel="nofollow"`, and see [chapter 9](09-corrections.md) for where this specific document was wrong)
- [Spring Boot 4.1.0 release announcement](https://spring.io/blog/2026/06/10/spring-boot-4/) (`rel="nofollow"`)