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
3.7 KiB
5. Launching a job, and why restart is not a separate API
← Previous | README | Next: The JDBC writer and Java records →
ImportRunner launches the job
with:
var params = new JobParametersBuilder()
.addString("batch.run", "demo") // identifying
.toJobParameters();
try {
var execution = jobOperator.start(job, params);
} catch (JobInstanceAlreadyCompleteException e) {
// already succeeded; nothing to do
}
JobParametersBuilder and addString(...) look exactly like Spring Batch 4 and 5. What changed
underneath is the type it builds: JobParameters is now an immutable record holding a
Set<JobParameter<?>>, and each JobParameter<T> is itself a record carrying its own name,
value, type, and an identifying flag (default true for the addX methods used here). None of
that shows up in this code — it matters if you ever construct a JobParameters by hand
instead of through the builder, or serialize one.
"Identifying" is the whole restart mechanism
A JobInstance is identified by a job name plus the set of parameters marked identifying.
Calling jobOperator.start(job, params) with parameters that match an existing JobInstance does
not create a second, independent run:
- If that instance's last execution is not
COMPLETED(itFAILED, or never finished), Spring Batch creates a newJobExecutionagainst the same instance and each step resumes from its own last-committed position. This is a restart, and it happens through the exact samestart()call as a first attempt — see chapter 7 for what "resumes from" means concretely. - If that instance's last execution
COMPLETED,start()throwsJobInstanceAlreadyCompleteException(unless the step allows re-running —allowStartIfComplete, used onreportStep; see chapter 8).
There is a separate JobOperator.restart(long executionId) method for restarting by execution
ID explicitly, useful for an operational tool that lists failed executions and lets someone pick
one. This module does not need it: every run uses the same identifying parameter
(batch.run=demo), so plain start() already does the right thing whether this is attempt one
or attempt two. If you want a fresh JobInstance on every run instead — the common pattern
for "run this daily" jobs — add a parameter that changes each time, typically a timestamp
or an incrementer, and mark it identifying (the default).
spring.batch.job.enabled
Spring Boot auto-configures a JobLauncherApplicationRunner that launches every Job bean it
finds using empty parameters, on every application startup. This module turns that off
(spring.batch.job.enabled: false in application.yml)
because ImportRunner needs to control the parameters (the identifying batch.run value) and
which profile's Job bean is active. Leaving both runners on would launch the job twice per
startup with two different parameter sets. If you only ever need "run the one job on startup
with no arguments," the auto-configured runner is simpler and this whole class is unnecessary.
Going deeper
JobParametersand identifying parameters: Spring Batch reference — running a job (rel="nofollow").spring.batch.job.namefor selecting which job the auto-configured runner launches, when you have more than oneJobbean and want to keep using it.