Spring Batch Non-Linear Flow and Job Instance Identity
Objective
A Spring Batch job is a sequence of steps, but two more concepts make that sequence useful in production: non-linear flow lets a job branch based on how a step actually finished (not just "next"), and the JobInstance/JobExecution model gives every run a strict identity so Spring Batch can tell "the same job, run again" apart from "the same run, retried after a failure."
Use Cases
- A job that behaves differently depending on outcome — e.g., skip a reporting step if a prior step reported nothing was skipped, but run it if something was.
- Launching the same daily import job with a
dateparameter and being able to trust that "June 27" only ever refers to one specific run, no matter how many times someone tries to launch it. - Distinguishing a genuine re-run attempt (should fail, because June 27 already completed) from a legitimate retry after a corrupted file was fixed (a new execution of the same instance, before it ever completed).
Deep Dive
Non-linear flow with a decision step
A step's outcome doesn't have to just move to a fixed "next" step — a decider can
inspect the execution and route the flow based on its status. In Java configuration,
JobExecutionDecider returns a FlowExecutionStatus, and JobBuilder/FlowBuilder
wire it into the flow:
javapublic class SkippedDecider implements JobExecutionDecider {
@Override
public FlowExecutionStatus decide(JobExecution jobExecution, StepExecution stepExecution) {
return new FlowExecutionStatus(hadSkips(stepExecution) ? "SKIPPED" : "CLEAN");
}
}
@Bean
public Job importProductsJob(JobRepository jobRepository, SkippedDecider decider,
Step readWrite, Step generateReport, Step sendReport, Step clean) {
return new JobBuilder("importProductsJob", jobRepository)
.start(readWrite)
.next(decider).on("SKIPPED").to(generateReport)
.from(decider).on("*").to(clean)
.from(generateReport).next(sendReport).next(clean)
.end()
.build();
}.next(decider) routes control to the decider, .on("STATUS") matches the string
the decider returned (*/? wildcards allowed), and .to(step) picks the branch —
the same branching semantics the book's XML <decision>/<next on="..."> elements
expressed declaratively.
TaskletStep and the Tasklet interface
Every step delegates its work to a Tasklet — TaskletStep is the implementation
application developers actually configure (Spring Batch also has FlowStep,
JobStep, and PartitionStep for composing jobs, but they wrap other jobs/flows
rather than doing work directly). A custom Tasklet is for one-off work like
decompressing a file; the chunk-oriented read-process-write pattern is itself
implemented as a built-in Tasklet (ChunkOrientedTasklet) under the hood.
JobInstance = Job + identifying JobParameters
A JobInstance is uniquely identified by the job plus the parameters used to launch
it:
javajobOperator.start(job, new JobParametersBuilder()
.addString("date", "2010-06-27")
.toJobParameters()
);Not every parameter has to participate in that identity. JobParameter carries an
identifying flag (true by default); a parameter explicitly marked non-identifying
— a run timestamp used only for logging, say — doesn't affect which JobInstance
a launch resolves to:
javanew JobParametersBuilder()
.addString("date", "2010-06-27") // identifying (default)
.addString("runTimestamp", Instant.now().toString(), false) // non-identifying
.toJobParameters();Job execution lifecycle rules
Three rules govern what happens when a job is launched:
- The first launch of a set of parameters creates both the
JobInstanceand its firstJobExecution. - Launching a
JobInstancethat already has a successfully completed execution throwsJobInstanceAlreadyCompleteException— Spring Batch refuses to silently re-run finished work. - Launching a
JobInstancethat already has an execution in progress throwsJobExecutionAlreadyRunningException— two concurrent executions of the same instance are never allowed.
A failed execution, by contrast, leaves the instance open: relaunching with the same parameters starts a new execution of the same instance rather than failing, which is what makes retrying a corrected job (like a re-uploaded, uncorrupted archive) possible.
Trade-offs
JobLauncher.run(job, jobParameters)— the call this book uses throughout — is deprecated as of Spring Batch 6.0.JobOperatornow extendsJobLauncherand addsstart(Job, JobParameters)as the recommended entry point;JobLauncheris slated for removal in 6.2+. Existing code built directly onjobLauncher.run(...)needs to move tojobOperator.start(...)before that removal lands.- Non-identifying parameters are convenient but easy to get backwards. Marking a
parameter that should distinguish runs (like the book's
date) as non-identifying by mistake silently collapses what should be separateJobInstances into one — the second launch either no-ops against a completed instance or throws, instead of running the job you intended. - XML's
<decision>element and the JavaJobBuilder/FlowBuilderchain express the same branching logic, but the Java form keeps the decider class and the wiring that references it in the same compiled, refactorable unit — renaming a decider bean in XML risks a silent mismatch that only surfaces at job-startup time, while the Java form fails to compile instead.
Documentation Links
- Cogoluègnes, Templier, Gregory, Bazoud, "Spring Batch in Action" (Manning, 2012) — Chapter 2, "Spring Batch concepts", sections 2.3.1-2.3.2, p. 44-50
- Spring Batch Reference — Controlling Step Flow (conditional flow, JobExecutionDecider)
- Spring Batch Reference — Domain Language (JobInstance, JobParameters, identifying flag)
- Spring Batch 6.0 Migration Guide — JobLauncher deprecated in favor of JobOperator