Files
spring-boot-demo/spring-batch/docs/02-anatomy-of-a-job.md
T
Claude b81af72bc3 Add spring-batch: jobs, steps, chunk processing and restartability on Boot 4.1
Companion code for "Spring Batch on Boot 4.1: Jobs, Steps, Chunk Processing and
Restartability". A productImportJob configured three ways by profile against a
poisoned CSV row, run as real java -jar processes (not just JUnit) so the
restart story is genuine: a chunk fails and rolls back, the process exits, a
brand-new JVM against the same file-based H2 database resumes at the exact
next unread row (READ_COUNT 20, not 60) and completes.

Findings the build pins:
- StepBuilder.chunk(int, PlatformTransactionManager) still compiles in Batch
  6.0.5 but returns the legacy SimpleStepBuilder; chunk(int) returns the new
  ChunkOrientedStepBuilder, and only the latter is used here.
- Two different ExecutionContext classes now exist in two different packages
  (infrastructure.item vs core.repository.persistence) with different shapes.
- spring-boot-starter-batch alone gives a resourceless JobRepository that
  forgets every JobInstance the moment the JVM exits; spring-boot-starter-
  batch-jdbc is what makes the restart demo possible at all, demonstrated by
  excluding BatchJdbcAutoConfiguration and watching a "restart" collide with
  the previous run's own data instead of resuming it.
- A migration-guide summary claiming CommandLineJobRunner was removed in 6.0
  is wrong -- javap against the real jar shows @Deprecated(forRemoval=true),
  not removed.
- RepeatStatus moved from core.repeat to infrastructure.repeat, caught by the
  compiler rather than by reading docs.

11 documentation chapters, 10 captured transcripts (unit tests, javap output,
and real two-JVM scenario runs), all regenerated by scripts/run-all.sh.

Fixed after push: three dead docs.spring.io links in the doc chapters
(readersAndWriters/* and chunk-oriented-processing/*.html paths moved when
Spring Batch 6 reorganized its reference docs; corrected to the current
readers-and-writers/*, processor.html and chunk-oriented-processing.html
paths, verified 200 via curl before committing).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019DXsJ1zpikbA1MQJN6RqFA
2026-09-13 06:37:46 +00:00

5.2 KiB

2. The anatomy of a job, and why the code looks different from older tutorials

← Previous | README | Next: Chunk-oriented processing →

BatchConfig in this module builds jobs and steps like this:

new JobBuilder("productImportJob", jobRepository)
        .start(importStep)
        .next(reportStep)
        .build();

new StepBuilder("importStep", jobRepository)
        .<Product, Product>chunk(10)
        .transactionManager(transactionManager)
        .reader(productReader)
        .processor(productProcessor)
        .writer(productWriter)
        .build();

If you have seen Spring Batch code before 2023 or so, two things here might look unfamiliar, and it is worth being precise about which is a genuine Spring Boot 4.1 change and which is just old:

  • JobBuilderFactory / StepBuilderFactory autowired as beans, then called as jobBuilderFactory.get("name")this was removed in Spring Batch 5, years before this article. It is not a Boot 4.1 surprise, it is a dead end that tutorials keep copy-pasting. JobBuilder and StepBuilder are plain classes you construct directly with a JobRepository, as above.

  • .chunk(10, transactionManager) — passing the transaction manager as a second argument to chunk()this compiles in 6.0.5, but it returns a different builder. StepBuilder.chunk(int) returns a ChunkOrientedStepBuilder, the model this whole module uses; StepBuilder.chunk(int, PlatformTransactionManager) returns the older SimpleStepBuilder, kept for the pre-6.0 chunk-processing model. Real javap output for both overloads, from this project's own spring-batch-core-6.0.5.jar: docs/output/03-stepbuilder-chunk-overloads.txt. The two builders overlap almost entirely in the methods you would reach for — reader/processor/writer/faultTolerant/skip exist on both — so the wrong choice usually still compiles and runs, and just quietly uses the legacy step implementation. Call .chunk(10).transactionManager(tx) (two calls) if you want ChunkOrientedStep.

    The fingerprint of picking the wrong overload. Your step still builds, runs, reads, writes and commits correctly — there is no error. The tell is in what you can't reach: ChunkOrientedStepBuilder-only options like retryPolicy(RetryPolicy) using Spring Framework 7's retry API, or a stack trace mentioning ChunkOrientedStep versus the older TaskletStep wrapping a ChunkOrientedTasklet. If you copied a two-argument chunk(10, txManager) from a pre-6.0 example and it "just worked", this is why nothing complained.

Where things moved

Spring Batch 6.0 (bundled with Spring Boot 4.1.1 as spring-batch.version 6.0.5, confirmed against spring-boot-dependencies-4.1.1.pom) reorganized packages fairly aggressively. The ones this module's code actually hits:

Old (Spring Batch 5.x) New (6.0.5)
org.springframework.batch.core.repeat.RepeatStatus org.springframework.batch.infrastructure.repeat.RepeatStatus
org.springframework.batch.item.* (readers, writers, ExecutionContext) org.springframework.batch.infrastructure.item.*
org.springframework.batch.core.JobParameters (mutable-ish, Map-backed) org.springframework.batch.core.job.parameters.JobParameters (immutable record, Set-backed)

The RepeatStatus move is the one this project's own reportTasklet hit directly — see chapter 9 for the exact compiler error it produced before the import was fixed. It is a good example of the article's verification rule in practice: rather than trusting a description of where things moved, write the code from the old import, let javac say package ... does not exist, and fix the import. That is faster and more reliable than reading a migration guide's prose, and chapter 9 has a case where trusting the prose produced a wrong claim.

JobRepository and JobOperator

Two interfaces you inject rather than configure by hand in this module:

  • JobRepository — every JobBuilder and StepBuilder above takes one as a constructor argument. In 6.0 it also extends JobExplorer (querying past executions), so there is one bean to inject instead of two.
  • JobOperator — what ImportRunner calls to start the job (see chapter 5). It extends JobLauncher and adds operational methods: restart(executionId), stop(executionId), recover(execution), getJobNames(). JobLauncher/JobExplorer still exist but are the deprecated half of this pair now.

Both are auto-configured by spring-boot-starter-batch-jdbc on this module's classpath; nothing in BatchConfig declares them as beans. Chapter 10 explains what you get instead if that starter is missing.

Next: Chunk-oriented processing →