Jobs, instances & parameters
|
This section documents the current Spring Batch line — 6.0.x, on Spring Framework 7 and Spring Boot 4.1.x, with a Java 17+ baseline — as published at the Spring Batch reference documentation. No specific patch version is pinned. Some surfaces (Spring Cloud Task and Spring Cloud Data Flow orchestration, the deployer-based partition handler, and JSR-352) are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. |
Four types carry the whole job-side vocabulary of Spring Batch: Job, JobInstance, JobParameters and
JobExecution. Getting the relationship between them right is what makes restart behave the way you expect.
Job
A Job is a container for an ordered sequence of `Step`s plus the configuration that applies to the run as a
whole — restartability, a parameter validator, an incrementer, and job-level listeners. It is a definition,
a singleton bean, and it holds no runtime state:
@Bean
public Job endOfDayJob(JobRepository jobRepository, Step loadStep, Step reportStep) {
return new JobBuilder("endOfDayJob", jobRepository)
.start(loadStep)
.next(reportStep)
.build();
}
The job’s name is its identity in the JobRepository; renaming a job starts its history over.
JobInstance: a job plus its identifying parameters
A JobInstance is a logical run of a job: the combination of the job name and its identifying
JobParameters. "The end-of-day job for 2026-01-31" is one JobInstance. Running it again on the same date
resumes that instance; running it with 2026-02-01 creates a new one.
This is the rule that surprises newcomers: Spring Batch refuses to start a JobInstance that has already
completed. A completed instance cannot be rerun — only a failed or stopped one can be restarted. If the
same logical work must genuinely run twice, it needs different identifying parameters (which is exactly what a
JobParametersIncrementer provides, below).
JobParameters
JobParameters is a typed map passed in at launch. Each JobParameter carries a value, its Java type, and an
identifying flag:
JobParameters parameters = new JobParametersBuilder()
.addLocalDate("run.date", LocalDate.of(2026, 1, 31)) // identifying by default
.addLong("run.id", 1L) // identifying by default
.addString("output.dir", "/tmp/out", false) // false => NOT identifying
.toJobParameters();
-
Typed:
addString,addLong,addDouble,addDate,addLocalDate,addLocalDateTime, and the genericaddJobParameter(key, value, Class<T>, identifying). Types are preserved in the metadata tables, so a parameter read back later is still aLocalDate, not a string. -
Identifying (the default) means the parameter takes part in deciding which
JobInstancethis is. Change it and you get a new instance — "start fresh". -
Non-identifying parameters are recorded and available to the job but do not affect instance identity. Output directories, verbosity flags and retry limits belong here, so that changing one on a restart does not accidentally create a new instance.
That distinction is precisely the difference between the two things an operator might mean by "run it again":
| Intent | What to do |
|---|---|
Resume where you left off — the previous run failed halfway |
Launch with the same identifying parameters. Spring Batch finds the existing |
Start fresh — process a new period, or reprocess from scratch |
Launch with different identifying parameters (a new business date, or a new |
Parameters are read inside a step through late binding, which is covered in Step flow & listeners, and validated as described in Configuring a job.
JobExecution
A JobExecution is one attempt at running a JobInstance. One instance may have many executions — one per
restart. It records:
-
BatchStatus— the framework’s own enum for where the execution stands; -
ExitStatus— the outcome as a code plus description, used by flow transitions and by callers such as a shell script; -
startTime,endTime,createTime,lastUpdated; -
failureExceptions— the throwables that ended it; -
the job-level
ExecutionContext(Steps, executions & the ExecutionContext); -
the `StepExecution`s it produced.
BatchStatus lifecycle
UNKNOWN is the remaining value: an execution whose fate could not be determined, typically after a process
crash. Recovering those is what the 6.0 JobOperator.recover() is for — see
Stopping, restart & recovery.
BatchStatus vs. ExitStatus
BatchStatus is a fixed enum owned by the framework. ExitStatus is an open, extensible pair of an exit
code (a String) and a description; a step or a listener may set any code it likes, and flow transitions
match on it:
@Bean
public StepExecutionListener classifyingListener() {
return new StepExecutionListener() {
@Override
public ExitStatus afterStep(StepExecution stepExecution) {
if (stepExecution.getWriteCount() == 0) {
return new ExitStatus("NO_DATA", "the input contained no rows to write");
}
return stepExecution.getExitStatus(); // leave COMPLETED as-is
}
};
}
.on("NO_DATA") in a job flow then routes on that code, as shown in
Step flow & listeners.
Two executions of one instance
The following job is deliberately fragile: it fails the first time and succeeds the second, so the same
JobInstance accumulates two `JobExecution`s.
@Configuration
public class RestartDemoConfiguration {
@Bean
public Step flakyStep(JobRepository jobRepository, PlatformTransactionManager tx) {
return new StepBuilder("flakyStep", jobRepository)
.tasklet((contribution, chunkContext) -> {
Path marker = Path.of("target", "already-ran");
if (!Files.exists(marker)) {
Files.createDirectories(marker.getParent());
Files.createFile(marker);
throw new IllegalStateException("first attempt always fails");
}
return RepeatStatus.FINISHED;
}, tx)
.build();
}
@Bean
public Job restartDemoJob(JobRepository jobRepository, Step flakyStep) {
return new JobBuilder("restartDemoJob", jobRepository)
.validator(new DefaultJobParametersValidator(
new String[] { "run.date" }, // required
new String[] { "output.dir" })) // optional
.start(flakyStep)
.build();
}
}
Launching it twice with the same identifying parameters produces two executions of one instance:
JobParameters parameters = new JobParametersBuilder()
.addLocalDate("run.date", LocalDate.of(2026, 1, 31))
.toJobParameters();
JobExecution first = jobOperator.start(restartDemoJob, parameters); // FAILED
JobExecution second = jobOperator.start(restartDemoJob, parameters); // COMPLETED
assert first.getJobInstance().equals(second.getJobInstance());
Forcing a new instance with an incrementer
When a job should be runnable repeatedly with otherwise identical parameters — a nightly job whose only
distinguishing feature is "another run" — attach a JobParametersIncrementer. RunIdIncrementer adds (or
bumps) a run.id parameter, so every launch through startNextInstance gets a fresh JobInstance:
@Bean
public Job nightlyJob(JobRepository jobRepository, Step flakyStep) {
return new JobBuilder("nightlyJob", jobRepository)
.incrementer(new RunIdIncrementer())
.start(flakyStep)
.build();
}
// each call creates the NEXT JobInstance rather than restarting the last one
jobOperator.startNextInstance(nightlyJob); // the Job bean, not its name
Custom incrementers — for example one that advances a business date — are shown in Configuring a job.