diff --git a/batch-5-to-6/.gitignore b/batch-5-to-6/.gitignore
new file mode 100644
index 0000000..2f7896d
--- /dev/null
+++ b/batch-5-to-6/.gitignore
@@ -0,0 +1 @@
+target/
diff --git a/batch-5-to-6/README.md b/batch-5-to-6/README.md
new file mode 100644
index 0000000..941783a
--- /dev/null
+++ b/batch-5-to-6/README.md
@@ -0,0 +1,13 @@
+# batch-5-to-6: Spring Batch 5 to 6 on Spring Boot 3.5 to 4.1
+
+Companion code for the ankurm.com post "Spring Batch 5 to 6 Migration Guide".
+
+| Folder | What it is |
+|---|---|
+| `legacy/` | A one-job app on Boot 3.5.16 / Batch 5.2.6 (JDK 21) using the Batch 5 API |
+| `migrated/` | The same app on Boot 4.1.1 / Batch 6.0.5 (JDK 25) using the Batch 6 API |
+| `scripts/run-all.sh` | Builds the intermediate stages from `legacy/` and regenerates every file in `output/` |
+| `output/` | Transcripts: compile errors, startup failure, deprecation warnings, the silent listener change, and the final run |
+
+Batch 6.0.5 is the latest GA; 6.1.0-M1 and 6.1.0-M2 are milestones and are not tested here.
+Tested on 28 September 2026.
diff --git a/batch-5-to-6/legacy/pom.xml b/batch-5-to-6/legacy/pom.xml
new file mode 100644
index 0000000..20adc7d
--- /dev/null
+++ b/batch-5-to-6/legacy/pom.xml
@@ -0,0 +1,21 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.5.16
+
+
+ com.ankurm
+ batch-legacy
+ 1.0.0
+ 21
+
+ org.springframework.bootspring-boot-starter-batch
+ com.h2databaseh2runtime
+ org.springframework.bootspring-boot-starter-testtest
+ org.springframework.batchspring-batch-testtest
+
+ org.springframework.bootspring-boot-maven-plugin
+
diff --git a/batch-5-to-6/legacy/src/main/java/com/ankurm/batch/App.java b/batch-5-to-6/legacy/src/main/java/com/ankurm/batch/App.java
new file mode 100644
index 0000000..23500d9
--- /dev/null
+++ b/batch-5-to-6/legacy/src/main/java/com/ankurm/batch/App.java
@@ -0,0 +1,14 @@
+package com.ankurm.batch;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+
+@SpringBootApplication
+public class App {
+ public static void main(String[] args) {
+ ConfigurableApplicationContext ctx = SpringApplication.run(App.class, args);
+ ctx.getBean(JobRunner.class).run();
+ ctx.close();
+ }
+}
diff --git a/batch-5-to-6/legacy/src/main/java/com/ankurm/batch/BatchConfig.java b/batch-5-to-6/legacy/src/main/java/com/ankurm/batch/BatchConfig.java
new file mode 100644
index 0000000..7c4c588
--- /dev/null
+++ b/batch-5-to-6/legacy/src/main/java/com/ankurm/batch/BatchConfig.java
@@ -0,0 +1,55 @@
+package com.ankurm.batch;
+
+import java.util.List;
+import org.springframework.batch.core.ChunkListener;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.Step;
+import org.springframework.batch.core.job.builder.JobBuilder;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.scope.context.ChunkContext;
+import org.springframework.batch.core.step.builder.StepBuilder;
+import org.springframework.batch.item.ItemReader;
+import org.springframework.batch.item.ItemWriter;
+import org.springframework.batch.item.support.ListItemReader;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.PlatformTransactionManager;
+
+@Configuration
+public class BatchConfig {
+
+ @Bean
+ ItemReader reader() {
+ return new ListItemReader<>(List.of(1, 2, 3, 4, 5, 6, 7));
+ }
+
+ @Bean
+ ItemWriter writer() {
+ return chunk -> System.out.println("WRITE " + chunk.getItems());
+ }
+
+ @Bean
+ ChunkListener chunkListener() {
+ return new ChunkListener() {
+ @Override public void afterChunk(ChunkContext context) {
+ System.out.println("CHUNK DONE, read so far=" + context.getStepContext().getStepExecution().getReadCount());
+ }
+ };
+ }
+
+ @Bean
+ Step squareStep(JobRepository repo, PlatformTransactionManager tx, ChunkListener listener) {
+ return new StepBuilder("squareStep", repo)
+ .chunk(3, tx)
+ .reader(reader())
+ .processor(i -> "sq" + (i * i))
+ .writer(writer())
+ .listener(listener)
+ .build();
+ }
+
+ @Bean
+ Job squareJob(JobRepository repo, Step squareStep) {
+ return new JobBuilder("squareJob", repo).start(squareStep).build();
+ }
+}
diff --git a/batch-5-to-6/legacy/src/main/java/com/ankurm/batch/JobRunner.java b/batch-5-to-6/legacy/src/main/java/com/ankurm/batch/JobRunner.java
new file mode 100644
index 0000000..ace3fe2
--- /dev/null
+++ b/batch-5-to-6/legacy/src/main/java/com/ankurm/batch/JobRunner.java
@@ -0,0 +1,33 @@
+package com.ankurm.batch;
+
+import org.springframework.batch.core.BatchStatus;
+import org.springframework.batch.core.Job;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.core.JobParameters;
+import org.springframework.batch.core.JobParametersBuilder;
+import org.springframework.batch.core.explore.JobExplorer;
+import org.springframework.batch.core.launch.JobLauncher;
+import org.springframework.stereotype.Component;
+
+@Component
+public class JobRunner {
+ private final JobLauncher launcher;
+ private final JobExplorer explorer;
+ private final Job job;
+
+ public JobRunner(JobLauncher launcher, JobExplorer explorer, Job squareJob) {
+ this.launcher = launcher; this.explorer = explorer; this.job = squareJob;
+ }
+
+ public void run() {
+ try {
+ JobParameters params = new JobParametersBuilder().addLong("run.id", 1L).toJobParameters();
+ JobExecution exec = launcher.run(job, params);
+ System.out.println("STATUS " + exec.getStatus() + " exit=" + exec.getExitStatus().getExitCode());
+ System.out.println("INSTANCES " + explorer.getJobInstanceCount("squareJob"));
+ System.out.println("DONE " + (exec.getStatus() == BatchStatus.COMPLETED));
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/batch-5-to-6/legacy/src/main/resources/application.properties b/batch-5-to-6/legacy/src/main/resources/application.properties
new file mode 100644
index 0000000..b08183f
--- /dev/null
+++ b/batch-5-to-6/legacy/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+spring.batch.job.enabled=false
+spring.datasource.url=jdbc:h2:mem:legacy;DB_CLOSE_DELAY=-1
+spring.batch.jdbc.initialize-schema=always
diff --git a/batch-5-to-6/legacy/src/test/java/com/ankurm/batch/JobTest.java b/batch-5-to-6/legacy/src/test/java/com/ankurm/batch/JobTest.java
new file mode 100644
index 0000000..4097ca2
--- /dev/null
+++ b/batch-5-to-6/legacy/src/test/java/com/ankurm/batch/JobTest.java
@@ -0,0 +1,23 @@
+package com.ankurm.batch;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.batch.core.BatchStatus;
+import org.springframework.batch.core.JobExecution;
+import org.springframework.batch.test.JobLauncherTestUtils;
+import org.springframework.batch.test.context.SpringBatchTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+@SpringBatchTest
+class JobTest {
+ @Autowired JobLauncherTestUtils utils;
+
+ @Test
+ void jobCompletes() throws Exception {
+ JobExecution exec = utils.launchJob();
+ assertThat(exec.getStatus()).isEqualTo(BatchStatus.COMPLETED);
+ }
+}
diff --git a/batch-5-to-6/migrated/pom.xml b/batch-5-to-6/migrated/pom.xml
new file mode 100644
index 0000000..03c0446
--- /dev/null
+++ b/batch-5-to-6/migrated/pom.xml
@@ -0,0 +1,22 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 4.1.1
+
+
+ com.ankurm
+ batch-migrated
+ 1.0.0
+ 25
+
+ org.springframework.bootspring-boot-starter-batch-jdbc
+ org.springframework.bootspring-boot-starter-jdbc
+ com.h2databaseh2runtime
+ org.springframework.bootspring-boot-starter-testtest
+ org.springframework.batchspring-batch-testtest
+
+ org.springframework.bootspring-boot-maven-plugin
+
diff --git a/batch-5-to-6/migrated/src/main/java/com/ankurm/batch/App.java b/batch-5-to-6/migrated/src/main/java/com/ankurm/batch/App.java
new file mode 100644
index 0000000..23500d9
--- /dev/null
+++ b/batch-5-to-6/migrated/src/main/java/com/ankurm/batch/App.java
@@ -0,0 +1,14 @@
+package com.ankurm.batch;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+
+@SpringBootApplication
+public class App {
+ public static void main(String[] args) {
+ ConfigurableApplicationContext ctx = SpringApplication.run(App.class, args);
+ ctx.getBean(JobRunner.class).run();
+ ctx.close();
+ }
+}
diff --git a/batch-5-to-6/migrated/src/main/java/com/ankurm/batch/BatchConfig.java b/batch-5-to-6/migrated/src/main/java/com/ankurm/batch/BatchConfig.java
new file mode 100644
index 0000000..6e7a3cf
--- /dev/null
+++ b/batch-5-to-6/migrated/src/main/java/com/ankurm/batch/BatchConfig.java
@@ -0,0 +1,56 @@
+package com.ankurm.batch;
+
+import java.util.List;
+import org.springframework.batch.core.listener.ChunkListener;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.step.Step;
+import org.springframework.batch.core.job.builder.JobBuilder;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.batch.core.step.builder.StepBuilder;
+import org.springframework.batch.infrastructure.item.Chunk;
+import org.springframework.batch.infrastructure.item.ItemReader;
+import org.springframework.batch.infrastructure.item.ItemWriter;
+import org.springframework.batch.infrastructure.item.support.ListItemReader;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.transaction.PlatformTransactionManager;
+
+@Configuration
+public class BatchConfig {
+
+ @Bean
+ ItemReader reader() {
+ return new ListItemReader<>(List.of(1, 2, 3, 4, 5, 6, 7));
+ }
+
+ @Bean
+ ItemWriter writer() {
+ return chunk -> System.out.println("WRITE " + chunk.getItems());
+ }
+
+ @Bean
+ ChunkListener chunkListener() {
+ return new ChunkListener<>() {
+ @Override public void afterChunk(Chunk chunk) {
+ System.out.println("CHUNK DONE, items in chunk=" + chunk.size());
+ }
+ };
+ }
+
+ @Bean
+ Step squareStep(JobRepository repo, PlatformTransactionManager tx, ChunkListener listener) {
+ return new StepBuilder("squareStep", repo)
+ .chunk(3)
+ .transactionManager(tx)
+ .reader(reader())
+ .processor(i -> "sq" + (i * i))
+ .writer(writer())
+ .listener(listener)
+ .build();
+ }
+
+ @Bean
+ Job squareJob(JobRepository repo, Step squareStep) {
+ return new JobBuilder("squareJob", repo).start(squareStep).build();
+ }
+}
diff --git a/batch-5-to-6/migrated/src/main/java/com/ankurm/batch/JobRunner.java b/batch-5-to-6/migrated/src/main/java/com/ankurm/batch/JobRunner.java
new file mode 100644
index 0000000..3fcfa2b
--- /dev/null
+++ b/batch-5-to-6/migrated/src/main/java/com/ankurm/batch/JobRunner.java
@@ -0,0 +1,33 @@
+package com.ankurm.batch;
+
+import org.springframework.batch.core.BatchStatus;
+import org.springframework.batch.core.job.Job;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.core.job.parameters.JobParameters;
+import org.springframework.batch.core.job.parameters.JobParametersBuilder;
+import org.springframework.batch.core.launch.JobOperator;
+import org.springframework.batch.core.repository.JobRepository;
+import org.springframework.stereotype.Component;
+
+@Component
+public class JobRunner {
+ private final JobOperator operator;
+ private final JobRepository repository;
+ private final Job job;
+
+ public JobRunner(JobOperator operator, JobRepository repository, Job squareJob) {
+ this.operator = operator; this.repository = repository; this.job = squareJob;
+ }
+
+ public void run() {
+ try {
+ JobParameters params = new JobParametersBuilder().addLong("run.id", 1L).toJobParameters();
+ JobExecution exec = operator.start(job, params);
+ System.out.println("STATUS " + exec.getStatus() + " exit=" + exec.getExitStatus().getExitCode());
+ System.out.println("INSTANCES " + repository.getJobInstanceCount("squareJob"));
+ System.out.println("DONE " + (exec.getStatus() == BatchStatus.COMPLETED));
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/batch-5-to-6/migrated/src/main/resources/application.properties b/batch-5-to-6/migrated/src/main/resources/application.properties
new file mode 100644
index 0000000..b08183f
--- /dev/null
+++ b/batch-5-to-6/migrated/src/main/resources/application.properties
@@ -0,0 +1,3 @@
+spring.batch.job.enabled=false
+spring.datasource.url=jdbc:h2:mem:legacy;DB_CLOSE_DELAY=-1
+spring.batch.jdbc.initialize-schema=always
diff --git a/batch-5-to-6/migrated/src/test/java/com/ankurm/batch/JobTest.java b/batch-5-to-6/migrated/src/test/java/com/ankurm/batch/JobTest.java
new file mode 100644
index 0000000..4320bf3
--- /dev/null
+++ b/batch-5-to-6/migrated/src/test/java/com/ankurm/batch/JobTest.java
@@ -0,0 +1,23 @@
+package com.ankurm.batch;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.batch.core.BatchStatus;
+import org.springframework.batch.core.job.JobExecution;
+import org.springframework.batch.test.JobOperatorTestUtils;
+import org.springframework.batch.test.context.SpringBatchTest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+@SpringBatchTest
+class JobTest {
+ @Autowired JobOperatorTestUtils utils;
+
+ @Test
+ void jobCompletes() throws Exception {
+ JobExecution exec = utils.startJob();
+ assertThat(exec.getStatus()).isEqualTo(BatchStatus.COMPLETED);
+ }
+}
diff --git a/batch-5-to-6/output/01-legacy-batch5.txt b/batch-5-to-6/output/01-legacy-batch5.txt
new file mode 100644
index 0000000..93db312
--- /dev/null
+++ b/batch-5-to-6/output/01-legacy-batch5.txt
@@ -0,0 +1,15 @@
+$ mvn test # Boot 3.5.16, Batch 5.2.6, JDK 21
+[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.750 s -- in com.ankurm.batch.JobTest
+[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
+[INFO] BUILD SUCCESS
+
+$ java -jar target/batch-legacy-1.0.0.jar
+WRITE [sq1, sq4, sq9]
+CHUNK DONE, read so far=3
+WRITE [sq16, sq25, sq36]
+CHUNK DONE, read so far=6
+WRITE [sq49]
+CHUNK DONE, read so far=7
+STATUS COMPLETED exit=COMPLETED
+INSTANCES 1
+DONE true
diff --git a/batch-5-to-6/output/02-compile-errors-after-bump.txt b/batch-5-to-6/output/02-compile-errors-after-bump.txt
new file mode 100644
index 0000000..ef16317
--- /dev/null
+++ b/batch-5-to-6/output/02-compile-errors-after-bump.txt
@@ -0,0 +1,10 @@
+$ mvn compile # legacy sources, only the Boot version changed to 4.1.1 (Batch 6.0.5)
+compile error lines reported: 56
+
+packages that no longer exist:
+package org.springframework.batch.core.explore does not exist
+package org.springframework.batch.item does not exist
+package org.springframework.batch.item.support does not exist
+
+classes that moved:
+ChunkListener ItemReader ItemWriter Job JobExecution JobExplorer JobParameters JobParametersBuilder ListItemReader Step
diff --git a/batch-5-to-6/output/03-imports-ported-no-transaction-manager.txt b/batch-5-to-6/output/03-imports-ported-no-transaction-manager.txt
new file mode 100644
index 0000000..b4d0c03
--- /dev/null
+++ b/batch-5-to-6/output/03-imports-ported-no-transaction-manager.txt
@@ -0,0 +1,19 @@
+$ mvn package # imports ported to the new packages, dependencies unchanged (spring-boot-starter-batch)
+[WARNING] s2/src/main/java/com/ankurm/batch/BatchConfig.java:[34,35] afterChunk(org.springframework.batch.core.scope.context.ChunkContext) in org.springframework.batch.core.listener.ChunkListener has been deprecated and marked for removal
+[WARNING] s2/src/main/java/com/ankurm/batch/BatchConfig.java:[43,17] chunk(int,org.springframework.transaction.PlatformTransactionManager) in org.springframework.batch.core.step.builder.StepBuilder has been deprecated and marked for removal
+[WARNING] s2/src/main/java/com/ankurm/batch/JobRunner.java:[14,19] org.springframework.batch.core.launch.JobLauncher in org.springframework.batch.core.launch has been deprecated and marked for removal
+[WARNING] s2/src/main/java/com/ankurm/batch/JobRunner.java:[15,19] org.springframework.batch.core.repository.explore.JobExplorer in org.springframework.batch.core.repository.explore has been deprecated and marked for removal
+[WARNING] s2/src/main/java/com/ankurm/batch/JobRunner.java:[18,22] org.springframework.batch.core.launch.JobLauncher in org.springframework.batch.core.launch has been deprecated and marked for removal
+[WARNING] s2/src/main/java/com/ankurm/batch/JobRunner.java:[18,44] org.springframework.batch.core.repository.explore.JobExplorer in org.springframework.batch.core.repository.explore has been deprecated and marked for removal
+[WARNING] s2/src/test/java/com/ankurm/batch/JobTest.java:[16,16] org.springframework.batch.test.JobLauncherTestUtils in org.springframework.batch.test has been deprecated and marked for removal
+[WARNING] s2/src/test/java/com/ankurm/batch/JobTest.java:[20,34] launchJob() in org.springframework.batch.test.JobLauncherTestUtils has been deprecated and marked for removal
+[INFO] BUILD SUCCESS
+
+$ java -jar target/batch-legacy-1.0.0.jar
+APPLICATION FAILED TO START
+***************************
+
+Description:
+
+Parameter 1 of method squareStep in com.ankurm.batch.BatchConfig required a bean of type 'org.springframework.transaction.PlatformTransactionManager' that could not be found.
+
diff --git a/batch-5-to-6/output/04-jdbc-starter-added.txt b/batch-5-to-6/output/04-jdbc-starter-added.txt
new file mode 100644
index 0000000..4f2448b
--- /dev/null
+++ b/batch-5-to-6/output/04-jdbc-starter-added.txt
@@ -0,0 +1,23 @@
+$ mvn package # + spring-boot-starter-batch-jdbc and spring-boot-starter-jdbc
+[WARNING] s3/src/main/java/com/ankurm/batch/BatchConfig.java:[34,35] afterChunk(org.springframework.batch.core.scope.context.ChunkContext) in org.springframework.batch.core.listener.ChunkListener has been deprecated and marked for removal
+[WARNING] s3/src/main/java/com/ankurm/batch/BatchConfig.java:[43,17] chunk(int,org.springframework.transaction.PlatformTransactionManager) in org.springframework.batch.core.step.builder.StepBuilder has been deprecated and marked for removal
+[WARNING] s3/src/main/java/com/ankurm/batch/JobRunner.java:[14,19] org.springframework.batch.core.launch.JobLauncher in org.springframework.batch.core.launch has been deprecated and marked for removal
+[WARNING] s3/src/main/java/com/ankurm/batch/JobRunner.java:[15,19] org.springframework.batch.core.repository.explore.JobExplorer in org.springframework.batch.core.repository.explore has been deprecated and marked for removal
+[WARNING] s3/src/main/java/com/ankurm/batch/JobRunner.java:[18,22] org.springframework.batch.core.launch.JobLauncher in org.springframework.batch.core.launch has been deprecated and marked for removal
+[WARNING] s3/src/main/java/com/ankurm/batch/JobRunner.java:[18,44] org.springframework.batch.core.repository.explore.JobExplorer in org.springframework.batch.core.repository.explore has been deprecated and marked for removal
+[WARNING] s3/src/test/java/com/ankurm/batch/JobTest.java:[16,16] org.springframework.batch.test.JobLauncherTestUtils in org.springframework.batch.test has been deprecated and marked for removal
+[WARNING] s3/src/test/java/com/ankurm/batch/JobTest.java:[20,34] launchJob() in org.springframework.batch.test.JobLauncherTestUtils has been deprecated and marked for removal
+[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.571 s -- in com.ankurm.batch.JobTest
+[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
+[INFO] BUILD SUCCESS
+
+$ java -jar target/batch-legacy-1.0.0.jar
+WRITE [sq1, sq4, sq9]
+CHUNK DONE, read so far=3
+WRITE [sq16, sq25, sq36]
+CHUNK DONE, read so far=6
+WRITE [sq49]
+CHUNK DONE, read so far=7
+STATUS COMPLETED exit=COMPLETED
+INSTANCES 1
+DONE true
diff --git a/batch-5-to-6/output/05-new-chunk-api-listener-silent.txt b/batch-5-to-6/output/05-new-chunk-api-listener-silent.txt
new file mode 100644
index 0000000..a416498
--- /dev/null
+++ b/batch-5-to-6/output/05-new-chunk-api-listener-silent.txt
@@ -0,0 +1,17 @@
+$ mvn package # chunk(3, tx) replaced by chunk(3).transactionManager(tx); listener untouched
+[WARNING] s4/src/main/java/com/ankurm/batch/BatchConfig.java:[34,35] afterChunk(org.springframework.batch.core.scope.context.ChunkContext) in org.springframework.batch.core.listener.ChunkListener has been deprecated and marked for removal
+[WARNING] s4/src/main/java/com/ankurm/batch/JobRunner.java:[14,19] org.springframework.batch.core.launch.JobLauncher in org.springframework.batch.core.launch has been deprecated and marked for removal
+[WARNING] s4/src/main/java/com/ankurm/batch/JobRunner.java:[15,19] org.springframework.batch.core.repository.explore.JobExplorer in org.springframework.batch.core.repository.explore has been deprecated and marked for removal
+[WARNING] s4/src/main/java/com/ankurm/batch/JobRunner.java:[18,22] org.springframework.batch.core.launch.JobLauncher in org.springframework.batch.core.launch has been deprecated and marked for removal
+[WARNING] s4/src/main/java/com/ankurm/batch/JobRunner.java:[18,44] org.springframework.batch.core.repository.explore.JobExplorer in org.springframework.batch.core.repository.explore has been deprecated and marked for removal
+[WARNING] s4/src/test/java/com/ankurm/batch/JobTest.java:[16,16] org.springframework.batch.test.JobLauncherTestUtils in org.springframework.batch.test has been deprecated and marked for removal
+[WARNING] s4/src/test/java/com/ankurm/batch/JobTest.java:[20,34] launchJob() in org.springframework.batch.test.JobLauncherTestUtils has been deprecated and marked for removal
+[INFO] BUILD SUCCESS
+
+$ java -jar target/batch-legacy-1.0.0.jar
+WRITE [sq1, sq4, sq9]
+WRITE [sq16, sq25, sq36]
+WRITE [sq49]
+STATUS COMPLETED exit=COMPLETED
+INSTANCES 1
+DONE true
diff --git a/batch-5-to-6/output/06-migrated.txt b/batch-5-to-6/output/06-migrated.txt
new file mode 100644
index 0000000..4f48a52
--- /dev/null
+++ b/batch-5-to-6/output/06-migrated.txt
@@ -0,0 +1,15 @@
+$ mvn package # final migrated project
+[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 4.029 s -- in com.ankurm.batch.JobTest
+[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
+[INFO] BUILD SUCCESS
+
+$ java -jar target/batch-migrated-1.0.0.jar
+WRITE [sq1, sq4, sq9]
+CHUNK DONE, items in chunk=3
+WRITE [sq16, sq25, sq36]
+CHUNK DONE, items in chunk=3
+WRITE [sq49]
+CHUNK DONE, items in chunk=1
+STATUS COMPLETED exit=COMPLETED
+INSTANCES 1
+DONE true
diff --git a/batch-5-to-6/output/07-api-facts.txt b/batch-5-to-6/output/07-api-facts.txt
new file mode 100644
index 0000000..3fb2335
--- /dev/null
+++ b/batch-5-to-6/output/07-api-facts.txt
@@ -0,0 +1,13 @@
+$ javap (Batch 6.0.5 jars), selected lines
+public interface core.launch.JobOperator extends core.launch.JobLauncher {
+public interface core.repository.JobRepository extends core.repository.explore.JobExplorer {
+public class test.JobOperatorTestUtils extends test.JobLauncherTestUtils {
+ public core.step.builder.SimpleStepBuilder chunk(int, PlatformTransactionManager);
+ public core.step.builder.ChunkOrientedStepBuilder chunk(int);
+ public core.step.builder.SimpleStepBuilder chunk(infrastructure.repeat.CompletionPolicy, PlatformTransactionManager);
+ public default void beforeChunk(core.scope.context.ChunkContext);
+ public default void afterChunk(core.scope.context.ChunkContext);
+ public default void afterChunkError(core.scope.context.ChunkContext);
+ public default void beforeChunk(infrastructure.item.Chunk);
+ public default void afterChunk(infrastructure.item.Chunk);
+ public default void onChunkError(java.lang.Exception, infrastructure.item.Chunk);
diff --git a/batch-5-to-6/scripts/run-all.sh b/batch-5-to-6/scripts/run-all.sh
new file mode 100755
index 0000000..91c5739
--- /dev/null
+++ b/batch-5-to-6/scripts/run-all.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+# Regenerates output/*.txt. Needs Maven, JDK 21 (J21) and JDK 25 (J25); network for Maven Central.
+set -u
+HERE=$(cd "$(dirname "$0")/.." && pwd); OUT=$HERE/output; W=${WORK:-/tmp/b56-work}
+J21=${J21:-/usr/lib/jvm/java-21-openjdk-amd64}; J25=${J25:-/tmp/tools/jdk-25.0.4.1+1}; MVN=${MVN:-mvn}
+mkdir -p "$OUT"; rm -rf "$W"; mkdir -p "$W"
+mv_() { local jh=$1 d=$2; shift 2; (cd "$d" && JAVA_HOME=$jh $MVN -B -Dmaven.compiler.showDeprecation=true "$@" 2>&1 | grep -v JAVA_TOOL_OPTIONS | sed "s#$W/##g"); }
+runjar() { local jh=$1 jar=$2; JAVA_HOME=$jh $jh/bin/java -jar "$jar" 2>&1 | grep -E '^(WRITE|CHUNK|STATUS|INSTANCES|DONE)'; }
+stage() { rm -rf "$W/$1"; cp -r "$HERE/$2" "$W/$1"; rm -rf "$W/$1/target"; }
+PORT_IMPORTS=$(cat <<'PY'
+import glob,sys
+m={'org.springframework.batch.core.ChunkListener':'org.springframework.batch.core.listener.ChunkListener',
+'org.springframework.batch.core.Job;':'org.springframework.batch.core.job.Job;',
+'org.springframework.batch.core.Step;':'org.springframework.batch.core.step.Step;',
+'org.springframework.batch.core.JobExecution;':'org.springframework.batch.core.job.JobExecution;',
+'org.springframework.batch.core.JobParameters;':'org.springframework.batch.core.job.parameters.JobParameters;',
+'org.springframework.batch.core.JobParametersBuilder':'org.springframework.batch.core.job.parameters.JobParametersBuilder',
+'org.springframework.batch.core.explore.JobExplorer':'org.springframework.batch.core.repository.explore.JobExplorer',
+'org.springframework.batch.item.':'org.springframework.batch.infrastructure.item.'}
+for f in glob.glob(sys.argv[1]+'/src/**/*.java',recursive=True):
+ s=open(f).read()
+ for a,b in m.items(): s=s.replace(a,b)
+ open(f,'w').write(s)
+PY
+)
+# 01 legacy: Boot 3.5.16 / Batch 5.2.6
+stage s0 legacy
+{ echo '$ mvn test # Boot 3.5.16, Batch 5.2.6, JDK 21'; mv_ "$J21" "$W/s0" package | grep -E 'Tests run:|BUILD'
+ echo; echo '$ java -jar target/batch-legacy-1.0.0.jar'; runjar "$J21" "$W/s0/target/batch-legacy-1.0.0.jar"; } > "$OUT/01-legacy-batch5.txt"
+# 02 same sources, Boot 4.1.1
+stage s1 legacy; sed -i 's/3.5.16/4.1.1/; s/21/25/' "$W/s1/pom.xml"
+{ echo '$ mvn compile # legacy sources, only the Boot version changed to 4.1.1 (Batch 6.0.5)'
+ mv_ "$J25" "$W/s1" compile > "$W/s1.log"
+ echo "compile error lines reported: $(grep -c '^\[ERROR\] .*\.java' "$W/s1.log")"; echo; echo 'packages that no longer exist:'
+ grep -oE 'package [a-z.]+ does not exist' "$W/s1.log" | sort -u; echo; echo 'classes that moved:'
+ grep -oE 'symbol: +class [A-Za-z]+' "$W/s1.log" | awk '{print $3}' | sort -u | tr '\n' ' '; echo; } > "$OUT/02-compile-errors-after-bump.txt"
+# 03 imports ported, dependencies untouched
+cp -r "$W/s1" "$W/s2"; rm -rf "$W/s2/target"; python3 -c "$PORT_IMPORTS" "$W/s2"
+{ echo '$ mvn package # imports ported to the new packages, dependencies unchanged (spring-boot-starter-batch)'
+ mv_ "$J25" "$W/s2" -DskipTests package | grep -E 'WARNING.*\.java|BUILD|ERROR.*\.java'
+ echo; echo '$ java -jar target/batch-legacy-1.0.0.jar'
+ JAVA_HOME=$J25 $J25/bin/java -jar "$W/s2/target/batch-legacy-1.0.0.jar" 2>&1 | grep -A6 'APPLICATION FAILED'; } > "$OUT/03-imports-ported-no-transaction-manager.txt"
+# 04 + JDBC starter
+cp -r "$W/s2" "$W/s3"; rm -rf "$W/s3/target"; python3 - "$W/s3/pom.xml" <<'PY'
+import sys; p=sys.argv[1]; s=open(p).read()
+s=s.replace('spring-boot-starter-batch','spring-boot-starter-batch-jdbc\n org.springframework.bootspring-boot-starter-jdbc')
+open(p,'w').write(s)
+PY
+{ echo '$ mvn package # + spring-boot-starter-batch-jdbc and spring-boot-starter-jdbc'
+ mv_ "$J25" "$W/s3" package | grep -E 'WARNING.*\.java|Tests run:|BUILD'
+ echo; echo '$ java -jar target/batch-legacy-1.0.0.jar'; runjar "$J25" "$W/s3/target/batch-legacy-1.0.0.jar"; } > "$OUT/04-jdbc-starter-added.txt"
+# 05 new chunk API but old listener
+cp -r "$W/s3" "$W/s4"; rm -rf "$W/s4/target"; sed -i 's/\.chunk(3, tx)/.chunk(3)\n .transactionManager(tx)/' "$W/s4/src/main/java/com/ankurm/batch/BatchConfig.java"
+{ echo '$ mvn package # chunk(3, tx) replaced by chunk(3).transactionManager(tx); listener untouched'
+ mv_ "$J25" "$W/s4" -DskipTests package | grep -E 'WARNING.*\.java|BUILD|ERROR.*\.java'
+ echo; echo '$ java -jar target/batch-legacy-1.0.0.jar'; runjar "$J25" "$W/s4/target/batch-legacy-1.0.0.jar"; } > "$OUT/05-new-chunk-api-listener-silent.txt"
+# 06 final migrated
+stage m migrated
+{ echo '$ mvn package # final migrated project'
+ mv_ "$J25" "$W/m" package | grep -E 'WARNING.*\.java|Tests run:|BUILD'
+ echo; echo '$ java -jar target/batch-migrated-1.0.0.jar'; runjar "$J25" "$W/m/target/batch-migrated-1.0.0.jar"; } > "$OUT/06-migrated.txt"
+# 07 API facts from the jars
+M2=${M2:-$HOME/.m2/repository/org/springframework/batch}
+CP=$M2/spring-batch-core/6.0.5/spring-batch-core-6.0.5.jar:$M2/spring-batch-infrastructure/6.0.5/spring-batch-infrastructure-6.0.5.jar:$M2/spring-batch-test/6.0.5/spring-batch-test-6.0.5.jar
+{ echo '$ javap (Batch 6.0.5 jars), selected lines'
+ for c in org.springframework.batch.core.launch.JobOperator org.springframework.batch.core.repository.JobRepository org.springframework.batch.test.JobOperatorTestUtils; do env -u JAVA_TOOL_OPTIONS $J25/bin/javap -cp $CP $c 2>&1 | grep -E '^public (interface|class)' | sed 's/org\.springframework\.batch\.//g'; done
+ env -u JAVA_TOOL_OPTIONS $J25/bin/javap -cp $CP org.springframework.batch.core.step.builder.StepBuilder 2>&1 | grep -E ' chunk\(' | sed 's/org\.springframework\.batch\.//g; s/org\.springframework\.transaction\.//g; s/org\.springframework\.//g'
+ env -u JAVA_TOOL_OPTIONS $J25/bin/javap -cp $CP org.springframework.batch.core.listener.ChunkListener 2>&1 | grep -E 'Chunk(Context|<)' | sed 's/org\.springframework\.batch\.//g'; } > "$OUT/07-api-facts.txt"
+echo done