Running a job
|
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. |
Spring Batch runs a job; it does not decide when. This page covers the launching API, the ways Spring Boot triggers it, and how to wire a job to an endpoint, a scheduler or a queue.
JobOperator is the launching interface
In 6.0 JobOperator extends JobLauncher, so one interface both launches and controls:
| Method | What it does |
|---|---|
|
Runs the given |
|
Applies the job’s |
|
Creates a new |
|
Requests a graceful stop; the execution moves to |
|
Marks a stopped or crashed execution |
|
(6.0) Cleans up executions stranded in |
Each of these takes a domain object. The older overloads that took a job name or an execution id
(startNextInstance(String), restart(long), stop(long), abandon(long)) are deprecated for removal in 6.0;
fetch the JobExecution from the JobRepository first and pass the object.
TaskExecutorJobOperator is the standard implementation. Its TaskExecutor decides the launching semantics:
@Bean
public JobOperator jobOperator(JobRepository jobRepository, JobRegistry jobRegistry,
PlatformTransactionManager transactionManager) throws Exception {
TaskExecutorJobOperator operator = new TaskExecutorJobOperator();
operator.setJobRepository(jobRepository);
operator.setJobRegistry(jobRegistry);
operator.setTransactionManager(transactionManager);
// synchronous by default (SyncTaskExecutor): start() returns when the job ends
operator.setTaskExecutor(new SimpleAsyncTaskExecutor("batch-")); // asynchronous
operator.afterPropertiesSet();
return operator;
}
-
Synchronous (the default
SyncTaskExecutor):start()blocks and returns a finishedJobExecution. Right for a command-line process whose exit code should reflect the outcome. -
Asynchronous (any real
TaskExecutor):start()returns as soon as the execution is created, typically inSTARTING. Right for an HTTP endpoint, which must not hold a request thread for an hour. The caller gets an execution id and polls the repository for progress.
Spring Boot: running at startup
Spring Boot registers a JobLauncherApplicationRunner that runs jobs after the context is ready. Two
properties control it:
spring:
batch:
job:
enabled: true # false => never run a job at startup
name: endOfDayJob # which job bean to run
Command-line arguments become JobParameters; non-option arguments in key=value form are converted by the
DefaultJobParametersConverter, and a trailing ,java.lang.Long style suffix pins the type:
java -jar batch-app.jar \
--spring.batch.job.name=endOfDayJob \
run.date=2026-01-31 \
chunk.size=500,java.lang.Long \
output.dir=/data/out
The process exits when the runner finishes. To make the exit code reflect the job outcome, add Boot’s
ExitCodeGenerator support:
@SpringBootApplication
public class BatchApplication {
public static void main(String[] args) {
System.exit(SpringApplication.exit(SpringApplication.run(BatchApplication.class, args)));
}
}
Boot’s side of this is documented at Batch Applications and on Spring Batch (SpringBoot Reference).
CommandLineJobOperator
For a standalone launch outside Spring Boot — a shell script, a scheduler that invokes java directly — Spring Batch 6.0 provides CommandLineJobOperator. It replaces the removed CommandLineJobRunner; scripts
that invoked the old class must be updated.
public final class BatchLauncher {
public static void main(String[] args) throws Exception {
try (ConfigurableApplicationContext context =
new AnnotationConfigApplicationContext(BatchInfrastructure.class,
EndOfDayJobConfiguration.class)) {
CommandLineJobOperator operator = new CommandLineJobOperator(
context.getBean(JobOperator.class),
context.getBean(JobRepository.class),
context.getBean(JobRegistry.class));
// start(String jobName, Properties parameters) -- the raw
// "key=value,type" argument has to be converted first
Properties parameters = StringUtils.splitArrayElementsIntoProperties(
args[1].split(","), "=");
int exitCode = operator.start(args[0], parameters);
System.exit(exitCode);
}
}
}
java -cp app.jar com.example.BatchLauncher endOfDayJob "run.date=2026-01-31,java.time.LocalDate"
It supports the same control operations as the API — start, stop, restart, abandon — so a scheduler can drive the whole lifecycle from the shell.
Launching on demand
From a REST endpoint
Launch asynchronously and hand the caller an execution id; never block the request thread on a batch job:
@RestController
@RequestMapping("/batch")
public class BatchController {
private final JobOperator jobOperator;
private final JobRepository jobRepository;
private final Job endOfDayJob;
public BatchController(JobOperator jobOperator, JobRepository jobRepository, Job endOfDayJob) {
this.jobOperator = jobOperator;
this.jobRepository = jobRepository;
this.endOfDayJob = endOfDayJob;
}
@PostMapping("/end-of-day")
public ResponseEntity<Map<String, Object>> launch(@RequestParam LocalDate runDate) throws Exception {
JobParameters parameters = new JobParametersBuilder()
.addLocalDate("run.date", runDate)
.toJobParameters();
JobExecution execution = jobOperator.start(endOfDayJob, parameters);
return ResponseEntity.accepted()
.body(Map.of("executionId", execution.getId(), "status", execution.getStatus()));
}
@GetMapping("/executions/{id}")
public Map<String, Object> status(@PathVariable long id) {
JobExecution execution = jobRepository.getJobExecution(id);
return Map.of("status", execution.getStatus(),
"exitCode", execution.getExitStatus().getExitCode());
}
}
Catch JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException and
JobParametersInvalidException and map them to 409/400 rather than letting them surface as 500.
From a scheduled method
@Component
public class EndOfDaySchedule {
private final JobOperator jobOperator;
private final Job endOfDayJob;
public EndOfDaySchedule(JobOperator jobOperator, Job endOfDayJob) {
this.jobOperator = jobOperator;
this.endOfDayJob = endOfDayJob;
}
@Scheduled(cron = "0 30 2 * * MON-FRI")
public void runNightly() throws Exception {
JobParameters parameters = new JobParametersBuilder()
.addLocalDate("run.date", LocalDate.now().minusDays(1))
.toJobParameters();
jobOperator.start(endOfDayJob, parameters);
}
}
In a multi-instance deployment, guard the schedule so only one node fires — see Scheduling & ShedLock.
From Quartz
A Quartz job is a thin adapter that resolves the JobOperator from the application context and launches:
public class BatchQuartzJob extends QuartzJobBean {
private JobOperator jobOperator;
private Job endOfDayJob;
public void setJobOperator(JobOperator jobOperator) { this.jobOperator = jobOperator; }
public void setEndOfDayJob(Job endOfDayJob) { this.endOfDayJob = endOfDayJob; }
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try {
jobOperator.start(endOfDayJob, new JobParametersBuilder()
.addLocalDate("run.date", LocalDate.now())
.toJobParameters());
} catch (Exception exception) {
throw new JobExecutionException(exception);
}
}
}
Launching from a message — a file-arrival event or a queue — is covered in Spring Batch Integration.
Graceful shutdown
A batch process that is killed mid-chunk leaves an execution in STARTED. To shut down cleanly:
-
request a stop first (
jobOperator.stop(jobExecution)), let the current chunk commit, and wait forSTOPPED; -
in a container, set
terminationGracePeriodSecondslong enough for one chunk plus a margin; -
register a shutdown hook, or rely on Spring Boot’s graceful shutdown, so
SIGTERMtriggers the stop instead of an abrupt exit; -
on the next start, use
JobOperator.recover()to clean up anything that was stranded anyway.
Both the stop protocol and recovery are detailed in Stopping, restart & recovery.