18 - Spring Batch
1. What is Spring Batch?
Spring Batch is a framework from the Spring ecosystem designed to process large volumes of data in a reliable, structured, and efficient way.
A batch job is typically a process that runs without direct user interaction and performs a large amount of work, such as:
- Importing thousands or millions of records.
- Exporting data to files.
- Processing CSV, XML, or JSON files.
- Migrating data between databases.
- Generating reports.
- Updating large numbers of database records.
- Processing financial transactions.
- Sending scheduled notifications.
- Data transformation and ETL processes.
For example, imagine that we receive a CSV file containing 10 million customers:
customers.csv
|
v
Read customer
|
v
Validate / Transform
|
v
Insert into Database
Doing this with a simple loop could consume a significant amount of memory and make error recovery difficult.
Spring Batch provides abstractions for handling these scenarios in a controlled way.
2. Why Use Spring Batch?
Spring Batch provides features that are especially useful when processing large amounts of data.
Main benefits
- Chunk-based processing
- Transaction management
- Job execution tracking
- Restartability
- Retry
- Skip
- Parallel processing
- Error handling
- Job parameters
- Execution metadata
- Integration with databases and files
Instead of implementing all these mechanisms manually, Spring Batch provides them as framework features.
3. Spring Batch vs a Normal Spring Service
Consider a normal Spring service:
@Service
public class CustomerService {
public void processCustomers(List<Customer> customers) {
for (Customer customer : customers) {
customerRepository.save(customer);
}
}
}
This can work for a small number of records.
However, imagine:
10 records -> Fine
1,000 records -> Probably fine
100,000 records -> Potential problems
10,000,000 -> Very problematic
A large list can consume a significant amount of memory.
Spring Batch allows us to process data incrementally:
10,000,000 records
|
v
Read 100
|
v
Process 100
|
v
Write 100
|
v
Commit Transaction
|
v
Read next 100
|
v
...
This is called chunk-oriented processing.
4. Spring Batch Architecture
A simplified Spring Batch architecture looks like this:
+----------------+
| Job Launcher |
+-------+--------+
|
v
+----------------+
| Job |
+-------+--------+
|
+--------+--------+
| |
v v
+---------+ +---------+
| Step 1 | | Step 2 |
+----+----+ +---------+
|
v
+---------------+
| Reader |
+-------+-------+
|
v
+---------------+
| Processor |
+-------+-------+
|
v
+---------------+
| Writer |
+-------+-------+
|
v
+---------------+
| Database |
+---------------+
The most important concepts are:
JobStepJobLauncherItemReaderItemProcessorItemWriterJobRepositoryJobExecutionStepExecutionJobParameters
5. Job
A Job represents an entire batch process.
For example:
Import Customers Job
The job could contain multiple steps:
Customer Import Job
Step 1 -> Read customer file
Step 2 -> Validate customers
Step 3 -> Insert customers
Step 4 -> Generate report
A job can therefore be viewed as a workflow.
Example:
@Bean
public Job customerJob(JobRepository jobRepository,
Step customerStep) {
return new JobBuilder("customerJob", jobRepository)
.start(customerStep)
.build();
}
6. Step
A Step represents an independent phase of a batch job.
For example:
Job
|
+-- Step 1: Read CSV
|
+-- Step 2: Validate records
|
+-- Step 3: Insert records
A job can contain one or many steps.
@Bean
public Job customerJob(JobRepository jobRepository,
Step step1,
Step step2) {
return new JobBuilder("customerJob", jobRepository)
.start(step1)
.next(step2)
.build();
}
7. JobLauncher
The JobLauncher is responsible for starting a Job.
Conceptually:
Application
|
v
JobLauncher
|
v
Job
|
v
Steps
Example:
jobLauncher.run(
customerJob,
new JobParameters()
);
A job can be started:
- Manually
- Through an API
- Through a scheduler
- Through Springβs scheduling mechanisms
- Through an external scheduler
For example:
Every night at 2 AM
Scheduler
|
v
JobLauncher
|
v
Customer Import Job
8. JobRepository
The JobRepository is responsible for storing metadata about batch executions.
Spring Batch needs to know things such as:
Which job ran?
When did it start?
When did it finish?
Did it succeed?
Which step failed?
How many records were processed?
Can the job be restarted?
This information is persisted in database tables.
Typical Spring Batch metadata tables include:
BATCH_JOB_INSTANCE
BATCH_JOB_EXECUTION
BATCH_JOB_EXECUTION_PARAMS
BATCH_STEP_EXECUTION
BATCH_STEP_EXECUTION_CONTEXT
BATCH_JOB_EXECUTION_CONTEXT
This metadata is one of the reasons Spring Batch can support restartability.
9. JobExecution
A JobExecution represents a specific execution of a Job.
For example:
Job:
customerImport
Execution #1
Status: FAILED
Execution #2
Status: COMPLETED
The same logical job can therefore have multiple executions.
10. JobParameters
JobParameters are parameters provided when launching a job.
For example:
customerImport
date = 2026-08-28
file = customers.csv
Example:
JobParameters parameters =
new JobParametersBuilder()
.addString("file", "customers.csv")
.addString("date", "2026-08-28")
.toJobParameters();
Parameters can also help identify different executions of the same job.
11. Chunk-Oriented Processing
One of the most important Spring Batch concepts is chunk processing.
Instead of processing everything in one transaction:
10,000 records
|
v
ONE HUGE TRANSACTION
Spring Batch can process records in chunks:
Chunk size = 100
Read 100
Process 100
Write 100
Commit
Read 100
Process 100
Write 100
Commit
Read 100
Process 100
Write 100
Commit
This provides several benefits:
- Lower memory usage
- Smaller transactions
- Better failure recovery
- Better database behavior
- Better scalability
12. ItemReader
The ItemReader is responsible for reading data.
Examples:
Database
CSV
XML
JSON
API
Message Queue
Conceptually:
public interface ItemReader<T> {
T read() throws Exception;
}
The reader returns one item at a time.
For example:
Reader
|
+--> Customer 1
+--> Customer 2
+--> Customer 3
+--> ...
Common implementations include:
JdbcCursorItemReader
JdbcPagingItemReader
FlatFileItemReader
13. ItemProcessor
The ItemProcessor is responsible for transforming or validating data.
For example:
@Bean
public ItemProcessor<Customer, Customer> processor() {
return customer -> {
customer.setName(
customer.getName().trim().toUpperCase()
);
return customer;
};
}
The flow is:
Input
|
v
Customer
|
v
Processor
|
v
Transformed Customer
The processor is also a good place for business validation.
14. ItemWriter
The ItemWriter writes the processed data.
For example:
Reader
|
v
Processor
|
v
Writer
|
v
Database
For database operations, Spring Batch provides JDBC-based writers such as:
JdbcBatchItemWriter
The writer typically receives a collection of items belonging to the current chunk.
15. Complete Chunk Processing
The complete flow is:
CHUNK
|
v
+---------------+
| ItemReader |
+-------+-------+
|
v
+---------------+
| ItemProcessor |
+-------+-------+
|
v
+---------------+
| ItemWriter |
+-------+-------+
|
v
Database
|
v
COMMIT
For example, with a chunk size of 100:
Read 100
|
Process 100
|
Write 100
|
Commit
16. Implementing Spring Batch in Spring Boot
Letβs create a simple application that reads customers and inserts them into a database.
Project Structure
A possible project structure:
src/main/java
βββ com.example.batch
βββ BatchApplication.java
βββ config
β βββ BatchConfig.java
βββ model
β βββ Customer.java
βββ repository
βββ CustomerRepository.java
17. Maven Dependencies
For a Spring Boot application:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
For production, you would typically replace H2 with a production database such as PostgreSQL or MySQL.
18. Domain Object
Letβs assume our application needs to import customers.
public record Customer(
Long id,
String name,
String email
) {
}
19. Database Table
Our destination table could be:
CREATE TABLE customer (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
email VARCHAR(255)
);
20. Creating the ItemReader
Suppose our source data is already in another database.
A JDBC paging reader is useful when processing large datasets.
@Bean
public JdbcPagingItemReader<Customer> reader(
DataSource dataSource) {
JdbcPagingItemReader<Customer> reader =
new JdbcPagingItemReader<>();
reader.setDataSource(dataSource);
reader.setPageSize(100);
reader.setRowMapper((rs, rowNum) ->
new Customer(
rs.getLong("id"),
rs.getString("name"),
rs.getString("email")
)
);
return reader;
}
The reader retrieves data in pages instead of loading the entire dataset into memory.
21. Creating the ItemProcessor
The processor can transform the data.
@Bean
public ItemProcessor<Customer, Customer> processor() {
return customer -> {
String normalizedName =
customer.name().trim();
return new Customer(
customer.id(),
normalizedName,
customer.email().toLowerCase()
);
};
}
22. Creating the ItemWriter
For inserting large amounts of data, use batch database operations rather than executing an individual SQL statement for every record.
@Bean
public JdbcBatchItemWriter<Customer> writer(
DataSource dataSource) {
return new JdbcBatchItemWriterBuilder<Customer>()
.sql("""
INSERT INTO customer
(id, name, email)
VALUES
(:id, :name, :email)
""")
.beanMapped()
.dataSource(dataSource)
.build();
}
23. Creating the Step
Now we connect the reader, processor, and writer.
@Bean
public Step customerStep(
JobRepository jobRepository,
PlatformTransactionManager transactionManager,
ItemReader<Customer> reader,
ItemProcessor<Customer, Customer> processor,
ItemWriter<Customer> writer) {
return new StepBuilder("customerStep", jobRepository)
.<Customer, Customer>chunk(100)
.transactionManager(transactionManager)
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
The important part is:
.chunk(100)
This means:
100 records
|
v
Read
|
v
Process
|
v
Write
|
v
COMMIT
24. Creating the Job
Finally, we create the Job.
@Bean
public Job customerJob(
JobRepository jobRepository,
Step customerStep) {
return new JobBuilder("customerJob", jobRepository)
.start(customerStep)
.build();
}
The complete architecture is now:
Customer Job
|
v
Customer Step
|
+----------+----------+
| | |
v v v
Reader Processor Writer
| | |
v v v
Source Transform Database
25. Complete Example
A simplified configuration could look like this:
@Configuration
@EnableBatchProcessing
public class BatchConfig {
@Bean
public Step customerStep(
JobRepository jobRepository,
PlatformTransactionManager transactionManager,
ItemReader<Customer> reader,
ItemProcessor<Customer, Customer> processor,
ItemWriter<Customer> writer) {
return new StepBuilder("customerStep", jobRepository)
.<Customer, Customer>chunk(100)
.transactionManager(transactionManager)
.reader(reader)
.processor(processor)
.writer(writer)
.build();
}
@Bean
public Job customerJob(
JobRepository jobRepository,
Step customerStep) {
return new JobBuilder("customerJob", jobRepository)
.start(customerStep)
.build();
}
}
26. Creating a Job to Insert Large Data
Now letβs look at a realistic scenario.
Imagine that we have:
customers.csv
containing:
1,John,john@email.com
2,Mary,mary@email.com
3,Robert,robert@email.com
...
10,000,000 records
We want to import everything into:
customer
The architecture would be:
CSV File
|
v
FlatFileItemReader
|
v
ItemProcessor
|
v
JdbcBatchItemWriter
|
v
Database
27. FlatFileItemReader
For CSV files, Spring Batch provides FlatFileItemReader.
Example:
@Bean
public FlatFileItemReader<Customer> reader() {
return new FlatFileItemReaderBuilder<Customer>()
.name("customerReader")
.resource(
new ClassPathResource("customers.csv")
)
.delimited()
.names("id", "name", "email")
.targetType(Customer.class)
.build();
}
The reader processes the file incrementally.
It does not need to load all 10 million records into memory.
28. Large Insert Flow
Suppose:
.chunk(500)
Then Spring Batch approximately works like this:
CSV
|
+--> Records 1-500
| |
| +--> Process
| |
| +--> INSERT 500
| |
| +--> COMMIT
|
+--> Records 501-1000
| |
| +--> Process
| |
| +--> INSERT 500
| |
| +--> COMMIT
|
+--> ...
This is much safer than:
List<Customer> customers =
readEntireFile();
repository.saveAll(customers);
for extremely large datasets.
29. Chunk Size
Chunk size is an important performance consideration.
Example:
.chunk(10)
means:
Read 10
Process 10
Write 10
Commit
While:
.chunk(1000)
means:
Read 1000
Process 1000
Write 1000
Commit
There is no universally correct chunk size.
A larger chunk can improve throughput but may:
- Increase memory usage
- Increase transaction duration
- Increase rollback cost
- Put more pressure on the database
A smaller chunk can:
- Reduce memory usage
- Produce shorter transactions
- Improve recovery granularity
But too-small chunks can increase overhead.
Therefore, the appropriate chunk size should be determined through testing and monitoring.
30. Transactions and Chunk Processing
Each chunk normally runs inside a transaction.
For example:
.chunk(100)
Conceptually:
BEGIN TRANSACTION
Read 100
Process 100
Write 100
COMMIT
If the writer fails:
BEGIN TRANSACTION
Read 100
Process 100
Write
ERROR
ROLLBACK
The chunk can therefore provide transactional consistency.
31. Skip
Sometimes a single invalid record should not cause the entire job to fail.
For example:
Record 1 -> OK
Record 2 -> OK
Record 3 -> INVALID
Record 4 -> OK
We may want:
Record 3 -> Skip
Spring Batch supports skip policies.
Example:
.faultTolerant()
.skip(ValidationException.class)
.skipLimit(100)
This means that up to 100 validation errors can be skipped.
32. Retry
Transient failures can sometimes be retried.
For example:
Database timeout
Network problem
Temporary external API failure
Example:
.faultTolerant()
.retry(TransientDataAccessException.class)
.retryLimit(3)
Conceptually:
Write
|
+--> Failure
|
+--> Retry #1
|
+--> Retry #2
|
+--> Retry #3
|
+--> Success
Retry should be used carefully.
A retry is appropriate for transient failures, not permanent validation errors.
33. Restartability
One of the biggest advantages of Spring Batch is the ability to restart failed jobs.
Imagine:
10,000,000 records
Processed:
7,500,000
Failure!
With a properly configured Spring Batch job, execution metadata can allow the job to restart from the appropriate point instead of necessarily starting from zero.
Conceptually:
Initial execution
1
2
3
...
7,500,000
X FAILURE
After fixing the problem:
Restart
7,500,001
7,500,002
...
10,000,000
This is especially valuable for long-running jobs.
34. Job Status
Spring Batch tracks execution status.
Common statuses include:
STARTING
STARTED
COMPLETED
FAILED
STOPPING
STOPPED
ABANDONED
UNKNOWN
For example:
customerImport
|
v
STARTED
|
v
COMPLETED
or:
customerImport
|
v
STARTED
|
v
FAILED
35. Scheduling Batch Jobs
Batch jobs are often executed on a schedule.
For example:
Every day at 2:00 AM
A Spring scheduler could trigger the job.
@Scheduled(cron = "0 0 2 * * *")
public void runJob() throws Exception {
jobLauncher.run(
customerJob,
new JobParametersBuilder()
.addLong(
"timestamp",
System.currentTimeMillis()
)
.toJobParameters()
);
}
For more complex enterprise environments, external schedulers can also trigger batch jobs.
Examples include:
Kubernetes CronJob
Airflow
Control-M
Jenkins
Cloud schedulers
36. Batch Processing vs Real-Time Processing
Spring Batch is designed for batch workloads, not necessarily real-time workloads.
Batch
Every night
10 million records
|
v
Process everything
Real-time
HTTP Request
|
v
Process one request
|
v
Return response
A REST API should generally not be replaced by Spring Batch simply because it needs to process data.
Choose the architecture according to the workload.
37. Common Use Cases
Spring Batch is commonly used for:
Data Migration
Database A
|
v
Spring Batch
|
v
Database B
File Import
CSV
|
v
Spring Batch
|
v
Database
File Export
Database
|
v
Spring Batch
|
v
CSV
Data Transformation
Raw Data
|
v
Validation
|
v
Transformation
|
v
Database
Periodic Processing
Every night
Database
|
v
Batch Job
|
v
Generate Reports
38. Best Practices
38.1 Use Chunk Processing
For large datasets, prefer:
.chunk(500)
over loading everything into memory.
38.2 Use Batch Database Operations
Prefer:
JdbcBatchItemWriter
or another batching mechanism over:
for (...) {
repository.save(item);
}
for very large workloads.
38.3 Keep Transactions Reasonably Small
Avoid extremely large transactions such as:
10 million records
|
v
ONE TRANSACTION
Prefer:
500 records
|
COMMIT
500 records
|
COMMIT
500 records
|
COMMIT
38.4 Make Jobs Restartable
Design jobs so that a failure does not require the entire process to start from scratch.
Think about:
Where did the job fail?
Can it continue?
Is the operation idempotent?
Can already-processed records be safely processed again?
38.5 Make Batch Operations Idempotent When Possible
Suppose a job inserts:
Customer #100
If the job is restarted, you donβt want:
Customer #100
Customer #100
Customer #100
Consider using:
- Unique constraints
- Upsert operations
- Business keys
- Deduplication
- Checkpoints
38.6 Monitor Batch Jobs
For production systems, monitor:
Job status
Execution duration
Records read
Records processed
Records written
Skip count
Retry count
Failure count
This makes operational problems much easier to identify.
38.7 Avoid Excessive Logging
Avoid logging every record:
log.info("Processing customer {}", customer.id());
for millions of records.
This can generate enormous log volumes and slow down the application.
Prefer aggregated information:
Processed 100,000 records
Processed 200,000 records
Processed 300,000 records
38.8 Tune the Chunk Size
Donβt assume:
.chunk(100)
is always optimal.
Test different values:
50
100
500
1000
5000
and measure:
- Throughput
- Memory usage
- Database load
- Transaction duration
- Failure recovery time
39. Common Mistakes
Loading Everything Into Memory
Bad:
List<Customer> customers =
repository.findAll();
for millions of records.
Better:
Paging Reader
+
Chunk Processing
One Transaction for Everything
Bad:
10 million records
|
v
ONE TRANSACTION
Better:
Chunk
|
Commit
|
Next Chunk
|
Commit
Individual Database Inserts
Bad:
for (Customer customer : customers) {
repository.save(customer);
}
for extremely large datasets.
Better:
JdbcBatchItemWriter
or an equivalent batch-oriented writer.
Ignoring Restartability
A batch job can run for hours.
If it fails at 95%:
95% complete
|
v
Failure
|
v
Restart everything
can be extremely expensive.
Design for restartability from the beginning.
40. Spring Batch Mental Model
A simple way to remember Spring Batch is:
JOB
|
+----------------------+
| |
v v
STEP 1 STEP 2
|
v
+--------+
| READER |
+---+----+
|
v
+---------+
|PROCESSOR|
+----+----+
|
v
+--------+
| WRITER |
+---+----+
|
v
DATABASE
And for large data:
LARGE DATASET
|
v
+-------------+
| Reader |
+------+------+
|
v
+-------------+
| Processor |
+------+------+
|
v
+-------------+
| Writer |
+------+------+
|
v
DATABASE
Everything happens in CHUNKS
41. Spring Batch vs Spring @Async
These concepts are different.
@Async
Designed primarily for executing work asynchronously:
Request
|
v
@Async
|
+----> Background execution
Spring Batch
Designed for structured, large-scale processing:
Job
|
+--> Step
|
+--> Reader
+--> Processor
+--> Writer
+--> Transaction
+--> Checkpoint
+--> Retry
+--> Skip
+--> Execution metadata
Spring Batch is therefore much more than simply running something in the background.
42. Key Takeaways
Spring Batch is a framework for reliable and scalable batch processing.
The most important concepts are:
Job
Step
JobLauncher
JobRepository
JobExecution
JobParameters
ItemReader
ItemProcessor
ItemWriter
Chunk
Transaction
Skip
Retry
Restartability
The most important processing model to remember is:
JOB
|
STEP
|
+-------+-------+
| | |
READER PROCESSOR WRITER
| | |
+-------+-------+
|
CHUNK
|
TRANSACTION
|
COMMIT
For large data insertion, the recommended approach is generally:
Large Dataset
|
v
Paging/File Reader
|
v
Processor
|
v
Batch Writer
|
v
Database
rather than:
Large Dataset
|
v
Load everything into memory
|
v
Loop
|
v
Individual INSERTs
The main goal of Spring Batch is not simply to process data in the background. It is to provide a structured, transactional, restartable, and scalable way of processing large amounts of data.