Spring Batch: Enterprise Batch Processing with Chunks and Tasks (2025 Guide)
Master Spring Batch 5.2 for high-volume batch jobs.
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
- ✓Java 17+
- ✓Spring Boot 3.4+ (or Spring Framework 6.2+)
- ✓Maven or Gradle
- ✓Basic knowledge of Spring Boot and JDBC
- ✓A relational database (H2 for dev, PostgreSQL/MySQL for prod)
• Spring Batch is a lightweight framework for building robust batch applications with chunk-oriented processing, retry, skip, and job restart capabilities. • Chunks read items, process them, and write them in configurable transactions — default chunk size is 1000. • Tasks (Tasklet) are for single-step operations like cleanup or file deletion, not for data processing. • JobRepository persists job execution metadata; use JDBC-backed for production, in-memory only for dev. • Always configure skip/retry for I/O failures — network blips and file locks are the top cause of batch failures in production.
Think of Spring Batch like a factory assembly line. Chunks are boxes of 1000 widgets: workers (readers) pick widgets, inspectors (processors) check them, and packers (writers) box them. If a widget is bad, you either skip it (and log it) or retry a few times. Tasks are like a single worker doing one-off jobs like sweeping the floor after the line stops. The JobRepository is the shift supervisor's logbook — it records every widget processed so if the power fails, you restart exactly where you left off, not from the beginning.
Batch processing isn't dead — it's the silent workhorse behind every end-of-day settlement, payroll run, and data warehouse ETL. I've seen teams waste weeks trying to reinvent chunk processing with raw JDBC loops, only to hit the same wall: no restartability, no skip logic, no metrics. Spring Batch 5.2 (the latest stable as of early 2025) handles all of that out of the box. In this guide, we'll build a real payment settlement batch job that processes 500,000 transactions per run. You'll learn chunk-oriented processing with ItemReader, ItemProcessor, and ItemWriter; how to configure fault tolerance with skip and retry; and how to debug jobs in production using the JobExplorer. We'll also cover Tasklet-based steps for operations like archiving processed files. If you're coming from Spring Batch 4.x, the key changes are: Java 17 baseline, removal of deprecated classes (like JobBuilderFactory), and new Micrometer-based metrics. This is not a "hello world" tutorial — we're going deep into the patterns that survive production incidents.
Setting Up Spring Batch 5.2 with Spring Boot 3.4
Let's start with the Maven dependencies. Spring Boot 3.4.x auto-configures Spring Batch with sensible defaults, but you need to opt into the batch starter. Here's the minimal pom.xml setup with H2 for development and PostgreSQL for production. Note that Spring Batch 5 removed the deprecated JobBuilderFactory and StepBuilderFactory — you now inject JobRepository and PlatformTransactionManager directly. The @EnableBatchProcessing annotation is still required to trigger auto-configuration. In production, you must configure a JDBC-based JobRepository (default is in-memory Map-based, which doesn't survive restarts). I've seen teams lose hours of processing because they forgot to switch to JDBC repository. Use the provided schema-*.sql scripts for your database — they create the BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_STEP_EXECUTION, and BATCH_JOB_EXECUTION_CONTEXT tables.
What the Official Docs Won't Tell You
The official Spring Batch reference documentation is excellent for the happy path, but it glosses over three critical gotchas. First: chunk size is not just a performance knob — it's a transaction boundary. A chunk of 1000 means your transaction commits every 1000 items. If you have a writer that calls a remote API and that API is slow, you'll hold a database transaction open for too long, causing deadlocks. Second: the default commit-interval is not configurable via properties — you must set it programmatically in the step builder. Third: skip limits are per-step, not per-chunk. If you set skipLimit(10), the entire step will fail after 10 skipped items across all chunks. This is a common source of confusion when a single bad record causes a cascade of skips. Also, the docs don't emphasize that ItemStream.open() is called before the first chunk, and close() after the last chunk — if your reader opens a file, it must implement ItemStream to get proper lifecycle management. I've debugged too many issues where a reader left file handles open because the developer didn't implement close().
Building a Payment Settlement Job with Chunk Processing
Let's build a real job: payment settlement. The reader fetches unsettled payments from a database, the processor calculates settlement amounts and applies fees, and the writer updates the database and sends a notification. We'll use JdbcCursorItemReader for the reader — it's efficient for large result sets because it uses a database cursor instead of loading everything into memory. The processor is where business logic lives: we compute the net settlement amount (transaction amount minus processing fee) and set the settlement date. The writer uses JdbcBatchItemWriter for batch updates — it's 10x faster than individual updates. Note that JdbcBatchItemWriter does not support transactions by itself; it relies on the chunk's transaction manager. Also, we must ensure idempotency: the job should be restartable. If it fails after writing 5000 items, on restart it should skip those 5000. Spring Batch handles this via the execution context, which stores the current position of the reader. For JdbcCursorItemReader, this works automatically if saveState=true (the default).
Fault Tolerance: Skip and Retry Strategies
In production, failures are inevitable. A network timeout, a deadlock, a malformed record. Spring Batch provides two mechanisms: skip and retry. Skip is for non-fatal errors where you can safely ignore a record (e.g., a null field). Retry is for transient errors that might succeed on retry (e.g., network timeout). Configure both in the step builder. Important: skip and retry are applied at the chunk level. If an item fails during processing, the entire chunk is rolled back, and the failed item is excluded. The remaining items in the chunk are re-processed. This means your processor must be idempotent — calling it twice on the same item should produce the same result. Also, be careful with skipLimit: if you set it too high, you might skip legitimate errors. I recommend starting with 10 and monitoring. For retry, use exponential backoff via the RetryTemplate. Spring Batch 5 integrates with Spring Retry, so you can configure a RetryTemplate with backoff policy. Never retry on the same thread without backoff — you'll overwhelm the resource.
Tasklet-Based Steps for One-Off Operations
Not every step needs chunk processing. Sometimes you need to perform a single operation: delete a temporary file, send a notification, or archive processed records. That's where Tasklet comes in. A Tasklet is a simple interface with one method: execute(). It returns RepeatStatus.FINISHED when done, or RepeatStatus.CONTINUABLE if you want it to repeat (rarely used). Tasklets run in a transaction — if the Tasklet throws an exception, the transaction rolls back. This is useful for operations that must be atomic. For example, after a chunk step processes payments, you might want to move the input file to an archive folder. If the move fails, you want the entire job to fail. Tasklets are also useful for cleanup steps before or after chunk processing. One caveat: Tasklets don't have built-in skip/retry logic. If you need fault tolerance in a Tasklet, you must implement it manually with try-catch. In practice, I use Tasklets only for operations that are idempotent and simple. For anything complex, use a chunk step with a reader that returns a single item.
Job Parameters, Execution Context, and Restartability
Job parameters are the input to your job — they differentiate job instances. Spring Batch uses them to determine if a job instance already exists. If you run a job with the same parameters twice, it returns the existing execution (unless you set 'run.id' parameter or use JobParametersIncrementer). The execution context stores state that survives restarts: reader position, processed count, etc. For chunk steps, Spring Batch manages this automatically. For Tasklets, you must save and restore state manually via StepExecution.getExecutionContext(). This is critical for restartability. If your Tasklet fails after moving 50 files, on restart it should skip those 50. You can store the list of processed files in the execution context. Also, never store large objects in the execution context — it's serialized to the database and can cause performance issues. I've seen a team store a 10MB JSON blob in the context, causing the BATCH_STEP_EXECUTION_CONTEXT table to bloat and queries to slow down. Use the context only for small state (strings, numbers, dates).
Monitoring and Observability with Micrometer and Actuator
Spring Batch 5 integrates with Micrometer to expose metrics: items read, processed, written, skipped, and retried. These metrics are available via Spring Boot Actuator's /actuator/metrics endpoint. You can also expose them to Prometheus or Datadog. In production, I always create a custom metrics dashboard that shows: chunk processing time, skip rate, retry rate, and job duration. The most important metric is 'spring.batch.chunk.write.count' — if it drops to zero while reads are happening, you have a writer bottleneck. Also, monitor 'spring.batch.job.duration' to detect regressions. Spring Batch Admin (now deprecated) is replaced by Micrometer + Actuator. For alerting, set up a threshold on skip rate: if skip rate exceeds 1% of total items, trigger an alert. Also, log the job parameters and execution ID at the start and end of each job. This helps correlate logs with specific job runs. I've seen teams waste hours trying to debug a job without knowing which execution ID caused the issue.
Testing Batch Jobs with Spring Batch Test
Testing batch jobs is tricky because of the database state and transaction boundaries. Spring Batch Test provides the JobLauncherTestUtils and StepMetaDataObjectFactory to simplify testing. Always test with an embedded database (H2) that mirrors your production schema. Test three scenarios: the happy path (all items succeed), the skip path (some items fail), and the retry path (transient failure). For skip testing, create a reader that returns a mix of valid and invalid items. Verify that the skip count matches expectations. For retry testing, mock a writer that fails the first two times and succeeds on the third. Also, test restartability: start a job, simulate a failure, then restart and verify it picks up where it left off. Use JobLauncherTestUtils.launchJob() for full integration tests. For unit tests, test the processor and writer in isolation. One common mistake: not cleaning the database between tests. Use @SpringBatchTest which automatically cleans the batch tables. Also, set spring.batch.job.enabled=false in test properties to prevent jobs from running on startup.
The Midnight Settlement That Took 14 Hours
read() method returned null only when a volatile flag was set, but a race condition prevented the flag from ever being set. The reader never signaled end-of-data, so the chunk kept reading the same 1000 items repeatedly, doing no writes.- Never write a custom ItemReader from scratch — use the provided readers (JdbcCursorItemReader, FlatFileItemReader) unless you have a very good reason.
- Always set maxItemCount and saveState=true on readers to prevent infinite loops.
- Monitor step duration with Micrometer and set alerts on anomalies.
SELECT * FROM BATCH_JOB_INSTANCE WHERE JOB_NAME = '<jobName>' ORDER BY JOB_INSTANCE_ID DESC LIMIT 1;SELECT * FROM BATCH_JOB_EXECUTION WHERE JOB_INSTANCE_ID = <id> ORDER BY CREATE_TIME DESC LIMIT 1;| File | Command / Code | Purpose |
|---|---|---|
| pom.xml | Setting Up Spring Batch 5.2 with Spring Boot 3.4 | |
| PaymentSettlementJobConfig.java | @Configuration | What the Official Docs Won't Tell You |
| PaymentSettlementComponents.java | @Bean | Building a Payment Settlement Job with Chunk Processing |
| FaultTolerantStepConfig.java | @Bean | Fault Tolerance |
| ArchiveTasklet.java | @Component | Tasklet-Based Steps for One-Off Operations |
| RestartableTasklet.java | @Component | Job Parameters, Execution Context, and Restartability |
| application.yml | management: | Monitoring and Observability with Micrometer and Actuator |
| PaymentSettlementJobTest.java | @SpringBatchTest | Testing Batch Jobs with Spring Batch Test |
Key takeaways
Interview Questions on This Topic
Explain how Spring Batch handles chunk-oriented processing. What happens when an item fails during processing?
Frequently Asked Questions
20+ years shipping production Java in banking & fintech. Lessons pulled from things that broke in production.
That's Spring Boot. Mark it forged?
5 min read · try the examples if you haven't