Files
spring-boot-demo/spring-batch/docs/05-launching-and-jobparameters.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

3.7 KiB

5. Launching a job, and why restart is not a separate API

← Previous | README | Next: The JDBC writer and Java records →

ImportRunner launches the job with:

var params = new JobParametersBuilder()
        .addString("batch.run", "demo")   // identifying
        .toJobParameters();
try {
    var execution = jobOperator.start(job, params);
} catch (JobInstanceAlreadyCompleteException e) {
    // already succeeded; nothing to do
}

JobParametersBuilder and addString(...) look exactly like Spring Batch 4 and 5. What changed underneath is the type it builds: JobParameters is now an immutable record holding a Set<JobParameter<?>>, and each JobParameter<T> is itself a record carrying its own name, value, type, and an identifying flag (default true for the addX methods used here). None of that shows up in this code — it matters if you ever construct a JobParameters by hand instead of through the builder, or serialize one.

"Identifying" is the whole restart mechanism

A JobInstance is identified by a job name plus the set of parameters marked identifying. Calling jobOperator.start(job, params) with parameters that match an existing JobInstance does not create a second, independent run:

  • If that instance's last execution is not COMPLETED (it FAILED, or never finished), Spring Batch creates a new JobExecution against the same instance and each step resumes from its own last-committed position. This is a restart, and it happens through the exact same start() call as a first attempt — see chapter 7 for what "resumes from" means concretely.
  • If that instance's last execution COMPLETED, start() throws JobInstanceAlreadyCompleteException (unless the step allows re-running — allowStartIfComplete, used on reportStep; see chapter 8).

There is a separate JobOperator.restart(long executionId) method for restarting by execution ID explicitly, useful for an operational tool that lists failed executions and lets someone pick one. This module does not need it: every run uses the same identifying parameter (batch.run=demo), so plain start() already does the right thing whether this is attempt one or attempt two. If you want a fresh JobInstance on every run instead — the common pattern for "run this daily" jobs — add a parameter that changes each time, typically a timestamp or an incrementer, and mark it identifying (the default).

spring.batch.job.enabled

Spring Boot auto-configures a JobLauncherApplicationRunner that launches every Job bean it finds using empty parameters, on every application startup. This module turns that off (spring.batch.job.enabled: false in application.yml) because ImportRunner needs to control the parameters (the identifying batch.run value) and which profile's Job bean is active. Leaving both runners on would launch the job twice per startup with two different parameter sets. If you only ever need "run the one job on startup with no arguments," the auto-configured runner is simpler and this whole class is unnecessary.

Going deeper

  • JobParameters and identifying parameters: Spring Batch reference — running a job (rel="nofollow").
  • spring.batch.job.name for selecting which job the auto-configured runner launches, when you have more than one Job bean and want to keep using it.

Next: The JDBC writer and Java records →