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
4.8 KiB
10. Resourceless vs. JDBC-backed: the dependency that makes chapter 7 possible
← Previous | README | Next: Production checklist →
Everything in chapter 7 depends on one dependency choice that is easy to get wrong on Spring Boot 4.1, because getting it wrong does not produce an error — the application starts, the job runs, nothing complains.
Spring Boot 4.1 split Batch autoconfiguration into separate modules rather than one monolith:
| Starter | Autoconfiguration class | What it provides |
|---|---|---|
spring-boot-starter-batch |
BatchAutoConfiguration |
Batch infrastructure basics; a resourceless (in-memory, non-persistent) JobRepository if nothing more specific is configured |
spring-boot-starter-batch-jdbc |
BatchJdbcAutoConfiguration |
A real JDBC-backed JobRepository, plus spring.batch.jdbc.initialize-schema to create the BATCH_* tables |
spring-boot-starter-batch-jdbc (this module's actual dependency, in
pom.xml) pulls in spring-boot-starter-batch transitively, so both
autoconfiguration classes are normally on the classpath together, and BatchJdbcAutoConfiguration
wins by providing the more specific JobRepository bean. Nothing about compiling or starting the
application tells you which one actually won.
Proving it, by turning the JDBC half off
--spring.autoconfigure.exclude=...BatchJdbcAutoConfiguration removes the JDBC-backed
JobRepository from the picture, leaving BatchAutoConfiguration's resourceless one as the only
candidate. Same job, same identifying job parameters, same PRODUCT table, run twice, in two
separate JVMs:
-- run 1 --
REPORT: 58 products now in the PRODUCT table
JOB FINISHED: status=COMPLETED exitCode=COMPLETED
-- run 2: a second, completely fresh JVM, same jar, same job parameters, same PRODUCT table --
org.springframework.dao.DuplicateKeyException: ... Unique index or primary key violation ...
VALUES ( /* 1 */ 'ABC-0001' )
JOB FINISHED: status=FAILED exitCode=FAILED
Full transcript: docs/output/11-resourceless-forgets-everything.txt.
Run 2 does not throw JobInstanceAlreadyCompleteException, the exception you would expect for
re-running an already-completed job (see chapter 5). It does
not know there was a run 1 at all. The resourceless JobRepository lives only in that JVM's
heap; when the process exits, every JobInstance, JobExecution, and StepExecution it ever
recorded goes with it. Run 2 starts productImportJob as if for the first time, tries to insert
ABC-0001 again, and collides with what run 1 already committed to the PRODUCT table —
a table that, unlike the job repository, is backed by the same file-based H2 database both
times.
This is the practical failure mode: the job repository's persistence and your own application
data's persistence are two separate decisions, and it is entirely possible to get the second
one right (a real database, a real table) while getting the first one wrong (resourceless,
because -jdbc was left off a dependency list or excluded by a profile without realizing what it
carried). The symptom is not a startup error. It is "my restart didn't restart" or "why did this
job double-insert everything," discovered in whatever environment happens to restart the JVM
between runs — which, for anything running in a container orchestrator, is any redeploy,
crash-restart, or scale-down/scale-up cycle.
How to check which one you actually have. Queryinformation_schema.tables(or your database's equivalent) for a table namedBATCH_JOB_INSTANCEafter your application starts. If it is not there, you have the resourceless job repository, whatever your dependency list looks like on paper — check for an exclusion inspring.autoconfigure.exclude, a conflictingJobRepositorybean of your own, or simply a missing-jdbc/-mongodbstarter.
Going deeper
BatchPropertiesandBatchJdbcProperties, includingspring.batch.job.enabled,spring.batch.jdbc.initialize-schema, andspring.batch.jdbc.table-prefix: thespring-configuration-metadata.jsoninsidespring-boot-batch-4.1.1.jarandspring-boot-batch-jdbc-4.1.1.jaris the authoritative list, more current than any blog post including this one.- MongoDB-backed job repositories follow the identical pattern via
spring-boot-starter-batch-data-mongodb, not covered by this module.