Spring Batch

This section documents Spring Boot 4.1.x and Spring Framework 7.0.x on the Java 17/21+ baseline, as described by the official Spring Boot reference documentation and each sub-project’s own site (Spring Data, Spring for Apache Kafka, Micrometer, Project Reactor, springdoc-openapi, Spring gRPC, and the others listed in this section’s Bibliography) — which are the references these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against those official docs before being relied on in production. Spring Boot and its ecosystem continue to evolve; the examples here target the current 4.1.x / 7.0.x releases.

This section’s bibliography lists the reference material consulted while preparing these pages.

Spring Batch is a framework for finite, bounded bulk processing: work that has a definite start and end, processes a known (if large) volume of data, and then terminates. This page is an overview of the model and of how Spring Boot wires it in. The dedicated in-depth guide is Spring Batch Reference; here the goal is only to place the moving parts and show one runnable job.

What batch processing is

For the full treatment of everything sketched below — the domain language, chunk and tasklet steps, fault tolerance, readers and writers, scaling, observability and testing — see Spring Batch Reference.

Batch processing is offline work over a bounded data set: nightly imports and exports, ETL into a warehouse, monthly statement or invoice generation, end-of-day reconciliation, bulk re-indexing, data-migration one-shots. The defining traits are that the input is finite, the job runs to completion and stops, and no user is waiting on the other end of a socket for the result.

That is a different shape from the two other common processing models:

  • Request/response (a REST call, an RPC) is small, synchronous, and latency-bound — one caller waits for one answer.

  • Streaming (Kafka consumers, reactive pipelines) is unbounded and long-lived — the process stays up indefinitely and reacts to events as they arrive.

Batch sits between them: large like a stream, but finite and restartable like a script. Spring Batch exists to give that kind of work a repeatable structure — chunking, transaction boundaries, failure tracking, and restart — instead of a hand-rolled loop in a main method.

Jobs, steps, and chunks

A Job is an ordered sequence of Step instances. Each step is one of two kinds:

  • A chunk-oriented step reads items one at a time with an ItemReader, optionally transforms each with an ItemProcessor, and buffers the results until a fixed chunk size (the commit interval) is reached, at which point an ItemWriter writes the whole chunk at once. The chunk boundary is the transaction / commit boundary: each chunk is processed inside its own transaction, so a failure in the middle of a job rolls back only the current chunk, and everything committed before it stays committed.

  • A Tasklet step is a single, indivisible unit of work — run a stored procedure, move a file, issue a DDL statement, call a remote service once. It runs inside one transaction and either succeeds or fails as a whole.

flowchart TB Job["Job"] --> Step1["Step 1 (chunk-oriented)"] Step1 --> Step2["Step 2 (Tasklet)"] subgraph chunk["Step 1: chunk loop -- one transaction per chunk of N items"] direction LR R["ItemReader\nread() one item"] --> P["ItemProcessor\nprocess() one item"] P --> Buf{"chunk full?\n(commit interval = N)"} Buf -- "no" --> R Buf -- "yes" --> W["ItemWriter\nwrite(list of N)"] W --> Commit["commit transaction\n+ update JobRepository"] Commit --> R end Step1 -.-> chunk

Most real jobs are a short pipeline of steps: a tasklet to stage a file, a chunk step to load it, a tasklet to archive it.

Job metadata and restartability

Spring Batch keeps a running record of every execution so a failed job can resume instead of starting over:

  • A JobInstance is a logical run of a job for a particular set of inputs — "the import for 2026-02-01".

  • JobParameters are the typed key/value pairs that identify a JobInstance. Re-launching a job with the same identifying parameters refers to the same instance; changing them starts a new one.

  • A JobExecution is a single attempt at running a JobInstance — it has a start time, an end time, and an exit status. One instance can have several executions if earlier attempts failed.

  • A StepExecution is the same idea one level down: one attempt at one step, tracking read/write/skip counts and its own status.

  • An ExecutionContext is a small persistent map attached to a job execution and to each step execution. It carries restart state — for example, "last line read was 5000" — so a restarted execution can skip past work already done.

All of this is persisted by the JobRepository into a fixed set of metadata tables (BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_STEP_EXECUTION, BATCH_*_EXECUTION_CONTEXT, …​). Because the repository knows which steps completed and what each ExecutionContext held, relaunching a failed job continues from the first incomplete step rather than from the beginning.

Skip and retry policies (tolerate N bad records, retry a transient failure M times) and the various listener interfaces (JobExecutionListener, StepExecutionListener, ItemReadListener, …​) exist for finer control of failure handling and cross-cutting hooks. See the Spring Batch reference for their full contracts.

Spring Boot integration

Add the starter:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-batch</artifactId>
</dependency>

With the starter on the classpath and a DataSource available, Spring Boot auto-configures a JDBC-backed JobRepository, a JobLauncher, and a PlatformTransactionManager against the application DataSource — no @Configuration boilerplate is needed to get the infrastructure. Key points:

  • spring.batch.jdbc.initialize-schema (always / embedded / never) controls whether Boot creates the metadata tables on startup. Use embedded (the default) for in-memory dev databases and a real migration tool (see Evolving the Database Model) to create them in staging/production, then set it to never.

  • Do not add @EnableBatchProcessing. On Spring Boot it turns the auto-configuration off and puts you back in charge of declaring the repository, launcher, and transaction manager yourself. Leave it out and let Boot configure them.

  • To run a job automatically on startup, set spring.batch.job.name to the job’s bean name (Boot runs the matching Job bean once the context is ready, passing any -- command-line arguments as JobParameters). To run on demand instead, inject JobLauncher and Job into a CommandLineRunner, a @Scheduled method, or a @RestController and call jobLauncher.run(job, params).

  • @SpringBatchTest wires up JobLauncherTestUtils and JobRepositoryTestUtils for integration tests that launch a job (or a single step) and assert on the resulting JobExecution.

A minimal chunk job that loads a CSV into a table:

@Configuration
public class ImportJobConfig {

    @Bean
    public FlatFileItemReader<PersonInput> personReader() {
        return new FlatFileItemReaderBuilder<PersonInput>()
                .name("personReader")
                .resource(new ClassPathResource("people.csv"))
                .delimited()
                .names("firstName", "lastName", "email")
                .targetType(PersonInput.class)
                .build();
    }

    @Bean
    public ItemProcessor<PersonInput, Person> personProcessor() {
        return in -> new Person(
                in.firstName().trim(),
                in.lastName().trim(),
                in.email().toLowerCase(Locale.ROOT));
    }

    @Bean
    public JdbcBatchItemWriter<Person> personWriter(DataSource dataSource) {
        return new JdbcBatchItemWriterBuilder<Person>()
                .dataSource(dataSource)
                .sql("INSERT INTO person (first_name, last_name, email) "
                        + "VALUES (:firstName, :lastName, :email)")
                // map named parameters explicitly: .beanMapped() reads JavaBean
                // getters, which a record does not expose
                .itemSqlParameterSourceProvider(p -> new MapSqlParameterSource()
                        .addValue("firstName", p.firstName())
                        .addValue("lastName", p.lastName())
                        .addValue("email", p.email()))
                .build();
    }

    @Bean
    public Step importStep(JobRepository jobRepository,
                           PlatformTransactionManager txManager,
                           FlatFileItemReader<PersonInput> personReader,
                           ItemProcessor<PersonInput, Person> personProcessor,
                           JdbcBatchItemWriter<Person> personWriter) {
        return new StepBuilder("importStep", jobRepository)
                .<PersonInput, Person>chunk(100, txManager)   // commit every 100 items
                .reader(personReader)
                .processor(personProcessor)
                .writer(personWriter)
                .build();
    }

    @Bean
    public Job importJob(JobRepository jobRepository, Step importStep) {
        return new JobBuilder("importJob", jobRepository)
                .start(importStep)
                .build();
    }
}

public record PersonInput(String firstName, String lastName, String email) {
}

public record Person(String firstName, String lastName, String email) {
}
spring:
  batch:
    job:
      name: importJob          # run this job on startup; omit to launch on demand
    jdbc:
      initialize-schema: embedded
  datasource:
    url: jdbc:postgresql://localhost:5432/app
    username: app
    password: app

new JobBuilder("importJob", jobRepository) and new StepBuilder("importStep", jobRepository) are the current builders — the old JobBuilderFactory / StepBuilderFactory types are gone. .chunk(100, txManager) sets both the commit interval and the transaction manager for the step.

Scaling (in brief)

When a single-threaded chunk loop is too slow, Spring Batch offers, roughly in order of complexity: a multi-threaded step (process chunks of one step on a thread pool), parallel steps (run independent steps concurrently via split flows), partitioning (split the input into ranges and run a copy of the same step per partition, locally or on remote workers), and remote chunking (send read items over messaging to worker nodes that process and write them). Each has trade-offs around ordering, restartability, and reader thread-safety; Scaling & Parallel Processing and the reference cover them in detail.