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
5.2 KiB
2. The anatomy of a job, and why the code looks different from older tutorials
← Previous | README | Next: Chunk-oriented processing →
BatchConfig in this module builds jobs and steps like this:
new JobBuilder("productImportJob", jobRepository)
.start(importStep)
.next(reportStep)
.build();
new StepBuilder("importStep", jobRepository)
.<Product, Product>chunk(10)
.transactionManager(transactionManager)
.reader(productReader)
.processor(productProcessor)
.writer(productWriter)
.build();
If you have seen Spring Batch code before 2023 or so, two things here might look unfamiliar, and it is worth being precise about which is a genuine Spring Boot 4.1 change and which is just old:
-
JobBuilderFactory/StepBuilderFactoryautowired as beans, then called asjobBuilderFactory.get("name")— this was removed in Spring Batch 5, years before this article. It is not a Boot 4.1 surprise, it is a dead end that tutorials keep copy-pasting.JobBuilderandStepBuilderare plain classes you construct directly with aJobRepository, as above. -
.chunk(10, transactionManager)— passing the transaction manager as a second argument tochunk()— this compiles in 6.0.5, but it returns a different builder.StepBuilder.chunk(int)returns aChunkOrientedStepBuilder, the model this whole module uses;StepBuilder.chunk(int, PlatformTransactionManager)returns the olderSimpleStepBuilder, kept for the pre-6.0 chunk-processing model. Realjavapoutput for both overloads, from this project's ownspring-batch-core-6.0.5.jar:docs/output/03-stepbuilder-chunk-overloads.txt. The two builders overlap almost entirely in the methods you would reach for —reader/processor/writer/faultTolerant/skipexist on both — so the wrong choice usually still compiles and runs, and just quietly uses the legacy step implementation. Call.chunk(10).transactionManager(tx)(two calls) if you wantChunkOrientedStep.The fingerprint of picking the wrong overload. Your step still builds, runs, reads, writes and commits correctly — there is no error. The tell is in what you can't reach:
ChunkOrientedStepBuilder-only options likeretryPolicy(RetryPolicy)using Spring Framework 7's retry API, or a stack trace mentioningChunkOrientedStepversus the olderTaskletStepwrapping aChunkOrientedTasklet. If you copied a two-argumentchunk(10, txManager)from a pre-6.0 example and it "just worked", this is why nothing complained.
Where things moved
Spring Batch 6.0 (bundled with Spring Boot 4.1.1 as spring-batch.version 6.0.5, confirmed
against spring-boot-dependencies-4.1.1.pom) reorganized packages fairly aggressively. The ones
this module's code actually hits:
| Old (Spring Batch 5.x) | New (6.0.5) |
|---|---|
org.springframework.batch.core.repeat.RepeatStatus |
org.springframework.batch.infrastructure.repeat.RepeatStatus |
org.springframework.batch.item.* (readers, writers, ExecutionContext) |
org.springframework.batch.infrastructure.item.* |
org.springframework.batch.core.JobParameters (mutable-ish, Map-backed) |
org.springframework.batch.core.job.parameters.JobParameters (immutable record, Set-backed) |
The RepeatStatus move is the one this project's own reportTasklet hit directly — see
chapter 9 for the exact compiler error it produced before the import was
fixed. It is a good example of the article's verification rule in practice: rather than trusting
a description of where things moved, write the code from the old import, let javac say
package ... does not exist, and fix the import. That is faster and more reliable than
reading a migration guide's prose, and chapter 9 has a case where trusting the prose produced a
wrong claim.
JobRepository and JobOperator
Two interfaces you inject rather than configure by hand in this module:
JobRepository— everyJobBuilderandStepBuilderabove takes one as a constructor argument. In 6.0 it also extendsJobExplorer(querying past executions), so there is one bean to inject instead of two.JobOperator— whatImportRunnercalls to start the job (see chapter 5). It extendsJobLauncherand adds operational methods:restart(executionId),stop(executionId),recover(execution),getJobNames().JobLauncher/JobExplorerstill exist but are the deprecated half of this pair now.
Both are auto-configured by spring-boot-starter-batch-jdbc on this module's classpath; nothing
in BatchConfig declares them as beans. Chapter 10 explains what
you get instead if that starter is missing.