# 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.