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

start(job, parameters)

Runs the given Job with exactly these parameters. Fails if the resulting instance already completed.

startNextInstance(job)

Applies the job’s JobParametersIncrementer and starts the next instance.

restart(jobExecution)

Creates a new JobExecution for the instance of a FAILED/STOPPED execution, resuming after the last completed step.

stop(jobExecution)

Requests a graceful stop; the execution moves to STOPPING and then STOPPED at the next checkpoint.

abandon(jobExecution)

Marks a stopped or crashed execution ABANDONED so it is skipped by restart logic.

recover(…​)

(6.0) Cleans up executions stranded in STARTED/STOPPING after a crash — see Stopping, restart & recovery.

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 finished JobExecution. 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 in STARTING. 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 for STOPPED;

  • in a container, set terminationGracePeriodSeconds long enough for one chunk plus a margin;

  • register a shutdown hook, or rely on Spring Boot’s graceful shutdown, so SIGTERM triggers 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.

Further reading