Reviving a dead Spring Batch side project from 2013 with AI

I wrote the original three-part Spring Batch blog series in 2013. The code worked back then, the posts got published, and then — like most side projects — it sat untouched on GitHub for over a decade. Java 1.5, Spring 3.1, Spring Batch 2.1, JUnit 3. It was a time capsule.

These are projects I would never have gone back and updated. The effort-to-reward ratio just isn’t there when you’re doing it manually. You’d spend hours chasing down deprecated APIs, fixing broken imports, untangling framework migration guides — all for a demo project that already works.

Fast forward to now (Mar 2026), and in less than an hour (over 2 days due to token resets), I used VSCode+Claude Code to vibe code the entire modernization.

Java 1.5 to 21. Spring 3.1 to 6.2. Spring Batch 2.1 to 5.2. JUnit 3 to 5. Mutable JavaBeans to immutable records. java.util.Date to java.time. double to BigDecimal. A hand-rolled DAO layer was replaced with a framework-provided layer JdbcBatchItemWriter. A single assertTrue(true) test expanded to 14 meaningful tests. Observability is added with MDC, Micrometer, and structured logging. Every change compiled, every test passed, and Claude caught edge cases I wouldn’t have thought to check — like test isolation failures from shared in-memory databases across Spring contexts.

The workflow was conversational: I’d ask Claude to review a category (Java language improvements, Spring Batch patterns, test coverage, observability), pick which recommendations to implement, and watch it make the changes, run the tests, and fix any issues. I never opened a migration guide or Stack Overflow tab.

This is the kind of work AI is great at — taking something that works but is outdated and methodically bringing it to current standards. Not greenfield creativity, but the unglamorous maintenance work that keeps codebases alive. For projects like this one, AI code-assist/vibe-coding tools (such as Claude Code) are the difference between “I should update that someday” and actually doing it.

It took me an hour (over 3 days) to wrap this up. Most of the time was spent waiting for my limited Claude tokens to reset and for me to eyeball the generated output (I still have trust issues with vibe coding). My only complaint, against myself, is that I need to eyeball the code even more.


Originally published as a three-part blog series in 2013, this guide has been updated for Java 21, Spring 6.2, and Spring Batch 5.2.

Spring Batch is a framework for building robust batch processing applications in Java. Whether you need to import a million-row CSV into a database, export records to a file, or run a scheduled maintenance task, Spring Batch provides the plumbing so you can focus on your business logic.

This guide walks through three progressively complex examples using a single Maven project. By the end, you’ll understand the core concepts and be ready to build your own batch jobs.

Core Concepts

Before diving into code, here are the building blocks you’ll work with in every Spring Batch application:

ConceptWhat It Does
JobThe entire batch process. Think of it as a container for one or more steps.
StepA single phase of work within a job. Steps execute sequentially.
TaskletA step that runs a single operation (no reader/writer). Good for simple tasks.
ChunkA step that reads, optionally processes, and writes data in configurable batches.
ItemReaderReads input data one item at a time (from a file, database, queue, etc.).
ItemProcessorTransforms or validates each item. Returns null to filter it out.
ItemWriterWrites a chunk of items to the destination (database, file, etc.).
JobRepositoryStores execution metadata (status, counts, timestamps) in a database.
JobLauncherKicks off a job with a set of parameters.

The key insight: for data-intensive work, you define a Reader → Processor → Writer pipeline and Spring Batch handles chunking, transactions, restart, and error recovery.

Project Setup

The project uses Maven with these core dependencies:

Key dependencies: spring-batch-core, spring-jdbc, hsqldb (embedded database), and slf4j/logback for logging. See the full pom.xml in the repository.

All three examples share common infrastructure (transaction manager, job repository, job launcher) defined once in base-batch-context.xml and imported by each job’s XML configuration.


Example 1: Simple Tasklet — Hello World

The simplest Spring Batch job: two steps that each run a Tasklet — a single unit of work with no reader/writer pipeline.

The Tasklets

Each tasklet implements the Tasklet interface with one method: execute(). It does its work and returns RepeatStatus.FINISHED.

The second tasklet simply logs the current time:

Job Configuration

The XML wires the two tasklets into a sequential two-step job:

step1 runs first, then step2. Each step wraps a tasklet reference. That’s it — no readers, writers, or chunk processing needed.

When to Use Tasklets

Tasklets are ideal for operations that don’t fit the read-process-write pattern: running a stored procedure, sending a notification, cleaning up temporary files, or any discrete operation within a larger batch job.


Example 2: Flat File to Database — The Reader-Processor-Writer Pipeline

This is where Spring Batch shines. We read ~200,000 ledger records from a CSV file, validate and normalize each record, and insert them into a database — all in about 2 seconds.

The Data

The input CSV (ledger.txt) looks like this:

The domain model is a Java record with BigDecimal for monetary precision:

Reader: CSV to Java Objects

Spring Batch’s FlatFileItemReader handles the file parsing. A custom FieldSetMapper converts each CSV row into a Ledger record, parsing dollar amounts like $50.00 and date fields:

Processor: Validate and Normalize

The ItemProcessor is optional but powerful. Ours does two things: filter out records with negative amounts (by returning null) and normalize the payment type to uppercase:

Returning null from a processor tells Spring Batch to skip that item — it won’t reach the writer.

Writer: Batch Insert

Instead of writing a custom DAO, we use Spring Batch’s built-in JdbcBatchItemWriter with named parameters that map directly to the record’s accessor methods:

The BeanPropertyItemSqlParameterSourceProvider automatically maps :receiptDate to ledger.receiptDate(), :memberName to ledger.memberName(), and so on. No manual JDBC code needed.

Chunk Configuration with Fault Tolerance

The job definition ties reader, processor, and writer together in a chunk-oriented step. Each chunk commits 1,000 records at a time, with policies for handling errors:

  • commit-interval="1000" — Read and process 1,000 items, then write them in a single transaction
  • skip-limit="10" — Tolerate up to 10 malformed CSV rows before failing the job
  • retry-limit="3" — Retry database deadlocks up to 3 times before giving up

This fault tolerance is declarative — no try/catch blocks in your business code.


Example 3: Database to Flat File — Reversing the Flow

The third example reads records from the database and writes them to a CSV file. It demonstrates that the reader/writer pattern works in both directions.

Reader: JDBC Cursor

JdbcCursorItemReader opens a database cursor and returns one Ledger per call to read(). A RowMapper converts each ResultSet row:

Writer: Flat File

FlatFileItemWriter with a DelimitedLineAggregator handles the CSV output. The BeanWrapperFieldExtractor selects which fields to include:

No processor is needed here — records pass directly from reader to writer.


Observability

All three jobs are instrumented with listeners that provide runtime visibility:

  • MDC context — Every log line includes [job=simpleJob step=step1] for correlation
  • Step metrics — Read, written, skipped, filtered, commit, and rollback counts with duration
  • Micrometer metrics — Job timers and counters recorded via SimpleMeterRegistry
  • Error logging — Read and write errors captured with full stack traces

Sample log output from the file-to-database job:

Testing

Each job has an integration test using @SpringBatchTest and JobLauncherTestUtils. The tests verify job completion status, item counts, and actual data:

Run all 14 tests with:

Key Takeaways

  1. Tasklets are for simple, discrete operations. Chunks are for data pipelines.
  2. Reader → Processor → Writer is the core pattern. The processor is optional.
  3. Spring Batch provides production-ready readers and writers for files, databases, and more — avoid writing custom DAO layers.
  4. Fault tolerance (skip, retry) is declarative in the chunk configuration.
  5. The JobRepository tracks every execution, making jobs restartable and auditable.
  6. @SpringBatchTest with JobLauncherTestUtils gives you integration testing for free.

Source Code

The complete project is available on GitHub: github.com/thomasma/springbatch3part

Technology Stack

ComponentVersion
Java21
Spring Framework6.2.3
Spring Batch5.2.2
Micrometer1.14.4
JUnit5.11.4
HSQLDB2.7.4
SLF4J + Logback2.0.16 / 1.5.16