# Spring Batch 6.0's fix: JobOperator#recover, then a normal restart

$ java -jar target/spring-batch-partitioning-1.0.0.jar --spring.profiles.active=recover \
    --recover.job-execution-id=1 --partition.shards-dir=./data/shards-small \
    --partition.grid-size=4 --partition.pool-core-size=4 --partition.pool-max-size=4

RECOVER: before -> status=FAILED
RECOVER:   step=ordersManagerStep status=FAILED
RECOVER:   step=ordersWorkerStep:partition3 status=STARTING
RECOVER:   step=ordersWorkerStep:partition2 status=STARTING
RECOVER:   step=ordersWorkerStep:partition1 status=COMPLETED
RECOVER:   step=ordersWorkerStep:partition0 status=STARTING
2026-09-14T09:13:31.156Z  INFO 3377 --- [           main] o.s.b.c.l.s.TaskExecutorJobOperator      : Recovering job execution: JobExecution: id=1, version=3, startTime=2026-09-14T09:11:21.331474360, endTime=2026-09-14T09:11:21.965090481, lastUpdated=2026-09-14T09:11:21.966521177, status=FAILED, exitStatus=exitCode=FAILED;exitDescription=org.springframework.batch.core.job.JobExecutionException: Partition handler returned an unsuccessful step
RECOVER: after  -> status=FAILED
RECOVER:   step=ordersManagerStep status=FAILED
RECOVER:   step=ordersWorkerStep:partition3 status=FAILED
RECOVER:   step=ordersWorkerStep:partition2 status=FAILED
RECOVER:   step=ordersWorkerStep:partition1 status=COMPLETED
RECOVER:   step=ordersWorkerStep:partition0 status=FAILED
JOB FINISHED: id=33 status=COMPLETED exitCode=COMPLETED

recover() walked the stuck JobExecution's StepExecutions and force-closed the three still at
STARTING to FAILED -- nothing else changed. RecoveryRunner runs at @Order(0); OrderIngestRunner
then runs its normal start() immediately after, in the same JVM, against the same shardsDir, and
this time it succeeds: a brand-new JobExecution (id=33) completes.

--- BATCH_STEP_EXECUTION after recovery + restart: which partitions actually reran ---
$ java -cp h2-2.4.240.jar org.h2.tools.Shell -url jdbc:h2:file:./data/rejecttest -user sa -password "" \
    -sql "SELECT JOB_EXECUTION_ID, STEP_EXECUTION_ID, STEP_NAME, STATUS, READ_COUNT FROM BATCH_STEP_EXECUTION WHERE JOB_EXECUTION_ID IN (1,33) ORDER BY JOB_EXECUTION_ID, STEP_EXECUTION_ID;"

JOB_EXECUTION_ID | STEP_EXECUTION_ID | STEP_NAME                   | STATUS    | READ_COUNT
1                | 1                 | ordersManagerStep           | FAILED    | 5000
1                | 2                 | ordersWorkerStep:partition3 | FAILED    | 0
1                | 3                 | ordersWorkerStep:partition2 | FAILED    | 0
1                | 4                 | ordersWorkerStep:partition1 | COMPLETED | 5000
1                | 5                 | ordersWorkerStep:partition0 | FAILED    | 0
33               | 33                | ordersManagerStep           | COMPLETED | 15000
33               | 34                | ordersWorkerStep:partition3 | COMPLETED | 5000
33               | 35                | ordersWorkerStep:partition2 | COMPLETED | 5000
33               | 36                | ordersWorkerStep:partition0 | COMPLETED | 5000
33               | 37                | reportStep                  | COMPLETED | 0

JobExecution 33 has exactly three new worker StepExecutions -- partition3, partition2, partition0,
the ones recover() marked FAILED. There is no new StepExecution for partition1: it stayed
COMPLETED from JobExecution 1 and was correctly skipped. Restart-only-the-failed-partition is not
a promise in the reference docs here -- it is what this table shows actually happened.
