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 anItemProcessor, and buffers the results until a fixed chunk size (the commit interval) is reached, at which point anItemWriterwrites 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
Taskletstep 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.
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
JobInstanceis a logical run of a job for a particular set of inputs — "the import for 2026-02-01". -
JobParametersare the typed key/value pairs that identify aJobInstance. Re-launching a job with the same identifying parameters refers to the same instance; changing them starts a new one. -
A
JobExecutionis a single attempt at running aJobInstance— it has a start time, an end time, and an exit status. One instance can have several executions if earlier attempts failed. -
A
StepExecutionis the same idea one level down: one attempt at one step, tracking read/write/skip counts and its own status. -
An
ExecutionContextis 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. Useembedded(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 tonever. -
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.nameto the job’s bean name (Boot runs the matchingJobbean once the context is ready, passing any--command-line arguments asJobParameters). To run on demand instead, injectJobLauncherandJobinto aCommandLineRunner, a@Scheduledmethod, or a@RestControllerand calljobLauncher.run(job, params). -
@SpringBatchTestwires upJobLauncherTestUtilsandJobRepositoryTestUtilsfor integration tests that launch a job (or a single step) and assert on the resultingJobExecution.
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.