From c1b40b0db9bf2a4549ddc6608b62f958995ea724 Mon Sep 17 00:00:00 2001 From: asmhatre Date: Mon, 28 Sep 2026 09:19:55 +0000 Subject: [PATCH] Add kafka4-migration module: Spring Kafka 3.3 to 4.1 migration on Boot 3.5 to 4.1 Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Gth99spB1CzQmBFofXmU4Z --- kafka4-migration/.gitignore | 1 + kafka4-migration/README.md | 13 ++++++ kafka4-migration/legacy/pom.xml | 21 +++++++++ .../src/main/java/com/ankurm/kafka/App.java | 9 ++++ .../java/com/ankurm/kafka/OrderEvent.java | 3 ++ .../java/com/ankurm/kafka/OrderListener.java | 18 ++++++++ .../java/com/ankurm/kafka/SerdeProbe.java | 14 ++++++ .../src/main/resources/application.properties | 8 ++++ .../java/com/ankurm/kafka/OrderFlowTest.java | 27 ++++++++++++ kafka4-migration/migrated/pom.xml | 21 +++++++++ .../src/main/java/com/ankurm/kafka/App.java | 9 ++++ .../java/com/ankurm/kafka/OrderEvent.java | 3 ++ .../java/com/ankurm/kafka/OrderListener.java | 18 ++++++++ .../java/com/ankurm/kafka/SerdeProbe.java | 14 ++++++ .../src/main/resources/application.properties | 8 ++++ .../java/com/ankurm/kafka/OrderFlowTest.java | 27 ++++++++++++ kafka4-migration/output/01-legacy-kafka3.txt | 12 +++++ .../output/02-boot4-no-starter.txt | 5 +++ .../output/03-starter-added-tests-pass.txt | 15 +++++++ .../04-runtime-classpath-has-no-jackson2.txt | 3 ++ kafka4-migration/output/05-migrated.txt | 13 ++++++ .../output/06-serializer-classes.txt | 2 + kafka4-migration/scripts/run-all.sh | 44 +++++++++++++++++++ 23 files changed, 308 insertions(+) create mode 100644 kafka4-migration/.gitignore create mode 100644 kafka4-migration/README.md create mode 100644 kafka4-migration/legacy/pom.xml create mode 100644 kafka4-migration/legacy/src/main/java/com/ankurm/kafka/App.java create mode 100644 kafka4-migration/legacy/src/main/java/com/ankurm/kafka/OrderEvent.java create mode 100644 kafka4-migration/legacy/src/main/java/com/ankurm/kafka/OrderListener.java create mode 100644 kafka4-migration/legacy/src/main/java/com/ankurm/kafka/SerdeProbe.java create mode 100644 kafka4-migration/legacy/src/main/resources/application.properties create mode 100644 kafka4-migration/legacy/src/test/java/com/ankurm/kafka/OrderFlowTest.java create mode 100644 kafka4-migration/migrated/pom.xml create mode 100644 kafka4-migration/migrated/src/main/java/com/ankurm/kafka/App.java create mode 100644 kafka4-migration/migrated/src/main/java/com/ankurm/kafka/OrderEvent.java create mode 100644 kafka4-migration/migrated/src/main/java/com/ankurm/kafka/OrderListener.java create mode 100644 kafka4-migration/migrated/src/main/java/com/ankurm/kafka/SerdeProbe.java create mode 100644 kafka4-migration/migrated/src/main/resources/application.properties create mode 100644 kafka4-migration/migrated/src/test/java/com/ankurm/kafka/OrderFlowTest.java create mode 100644 kafka4-migration/output/01-legacy-kafka3.txt create mode 100644 kafka4-migration/output/02-boot4-no-starter.txt create mode 100644 kafka4-migration/output/03-starter-added-tests-pass.txt create mode 100644 kafka4-migration/output/04-runtime-classpath-has-no-jackson2.txt create mode 100644 kafka4-migration/output/05-migrated.txt create mode 100644 kafka4-migration/output/06-serializer-classes.txt create mode 100755 kafka4-migration/scripts/run-all.sh diff --git a/kafka4-migration/.gitignore b/kafka4-migration/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/kafka4-migration/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/kafka4-migration/README.md b/kafka4-migration/README.md new file mode 100644 index 0000000..513657a --- /dev/null +++ b/kafka4-migration/README.md @@ -0,0 +1,13 @@ +# kafka4-migration: Spring Kafka 3 to 4 on Spring Boot 3.5 to 4.1 + +Companion code for the ankurm.com post "Spring Kafka 3 to 4 Migration Guide". + +| Folder | What it is | +|---|---| +| `legacy/` | Producer, `@KafkaListener` and an `@EmbeddedKafka` test on Boot 3.5.16 / Spring Kafka 3.3.16 / kafka-clients 3.9.2 (JDK 21), using the Jackson 2 `JsonSerializer` | +| `migrated/` | The same app on Boot 4.1.1 / Spring Kafka 4.1.1 / kafka-clients 4.2.1 (JDK 25), using `spring-boot-starter-kafka` and `JacksonJsonSerializer` | +| `scripts/run-all.sh` | Builds the intermediate stages from `legacy/` and regenerates every file in `output/` | +| `output/` | Transcripts for each stage | + +Spring Kafka 4.1.1 is the latest GA; 4.2.0-M1 and 4.2.0-M2 are milestones and are not tested here. +No external broker is needed: the tests use `@EmbeddedKafka`. Tested on 28 September 2026. diff --git a/kafka4-migration/legacy/pom.xml b/kafka4-migration/legacy/pom.xml new file mode 100644 index 0000000..e2f2088 --- /dev/null +++ b/kafka4-migration/legacy/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.5.16 + + + com.ankurm + kafka-legacy + 1.0.0 + 21 + + org.springframework.bootspring-boot-starter + org.springframework.kafkaspring-kafka + org.springframework.bootspring-boot-starter-json + org.springframework.bootspring-boot-starter-testtest + org.springframework.kafkaspring-kafka-testtest + + diff --git a/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/App.java b/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/App.java new file mode 100644 index 0000000..7e5a6b8 --- /dev/null +++ b/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/App.java @@ -0,0 +1,9 @@ +package com.ankurm.kafka; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class App { + public static void main(String[] args) { SpringApplication.run(App.class, args); } +} diff --git a/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/OrderEvent.java b/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/OrderEvent.java new file mode 100644 index 0000000..758f051 --- /dev/null +++ b/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/OrderEvent.java @@ -0,0 +1,3 @@ +package com.ankurm.kafka; + +public record OrderEvent(String id, int qty) { } diff --git a/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/OrderListener.java b/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/OrderListener.java new file mode 100644 index 0000000..12da68d --- /dev/null +++ b/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/OrderListener.java @@ -0,0 +1,18 @@ +package com.ankurm.kafka; + +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.List; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.stereotype.Component; + +@Component +public class OrderListener { + public final List received = new CopyOnWriteArrayList<>(); + + @KafkaListener(topics = "orders") + void on(OrderEvent event, @Header(KafkaHeaders.RECEIVED_KEY) String key) { + received.add("key=" + key + " id=" + event.id() + " qty=" + event.qty()); + } +} diff --git a/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/SerdeProbe.java b/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/SerdeProbe.java new file mode 100644 index 0000000..b2dd064 --- /dev/null +++ b/kafka4-migration/legacy/src/main/java/com/ankurm/kafka/SerdeProbe.java @@ -0,0 +1,14 @@ +package com.ankurm.kafka; + +import java.nio.charset.StandardCharsets; +import org.springframework.kafka.support.serializer.JsonSerializer; + +/** Serialises one event with the JSON serializer, using only the runtime classpath (no test jars). */ +public class SerdeProbe { + public static void main(String[] args) { + try (JsonSerializer s = new JsonSerializer<>()) { + byte[] bytes = s.serialize("orders", new OrderEvent("o-1", 3)); + System.out.println("SERIALIZED " + new String(bytes, StandardCharsets.UTF_8)); + } + } +} diff --git a/kafka4-migration/legacy/src/main/resources/application.properties b/kafka4-migration/legacy/src/main/resources/application.properties new file mode 100644 index 0000000..17a95b6 --- /dev/null +++ b/kafka4-migration/legacy/src/main/resources/application.properties @@ -0,0 +1,8 @@ +spring.kafka.consumer.group-id=orders-group +spring.kafka.consumer.auto-offset-reset=earliest +spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer +spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer +spring.kafka.consumer.properties.spring.json.trusted.packages=com.ankurm.kafka +spring.kafka.consumer.properties.spring.json.value.default.type=com.ankurm.kafka.OrderEvent diff --git a/kafka4-migration/legacy/src/test/java/com/ankurm/kafka/OrderFlowTest.java b/kafka4-migration/legacy/src/test/java/com/ankurm/kafka/OrderFlowTest.java new file mode 100644 index 0000000..5f878ea --- /dev/null +++ b/kafka4-migration/legacy/src/test/java/com/ankurm/kafka/OrderFlowTest.java @@ -0,0 +1,27 @@ +package com.ankurm.kafka; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.test.EmbeddedKafkaBroker; +import org.springframework.kafka.test.context.EmbeddedKafka; + +@SpringBootTest(properties = "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}") +@EmbeddedKafka(topics = "orders", partitions = 1) +class OrderFlowTest { + @Autowired KafkaTemplate template; + @Autowired OrderListener listener; + @Autowired EmbeddedKafkaBroker broker; + + @Test + void sendAndReceive() { + template.send("orders", "k1", new OrderEvent("o-1", 3)); + await().untilAsserted(() -> assertThat(listener.received).hasSize(1)); + System.out.println("RESULT " + listener.received.get(0)); + System.out.println("BROKERS " + broker.getBrokersAsString().replaceAll(":\\d+", ":PORT")); + } +} diff --git a/kafka4-migration/migrated/pom.xml b/kafka4-migration/migrated/pom.xml new file mode 100644 index 0000000..9b75163 --- /dev/null +++ b/kafka4-migration/migrated/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.1.1 + + + com.ankurm + kafka-migrated + 1.0.0 + 25 + + org.springframework.bootspring-boot-starter + org.springframework.bootspring-boot-starter-kafka + org.springframework.bootspring-boot-starter-jackson + org.springframework.bootspring-boot-starter-testtest + org.springframework.kafkaspring-kafka-testtest + + diff --git a/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/App.java b/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/App.java new file mode 100644 index 0000000..7e5a6b8 --- /dev/null +++ b/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/App.java @@ -0,0 +1,9 @@ +package com.ankurm.kafka; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class App { + public static void main(String[] args) { SpringApplication.run(App.class, args); } +} diff --git a/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/OrderEvent.java b/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/OrderEvent.java new file mode 100644 index 0000000..758f051 --- /dev/null +++ b/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/OrderEvent.java @@ -0,0 +1,3 @@ +package com.ankurm.kafka; + +public record OrderEvent(String id, int qty) { } diff --git a/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/OrderListener.java b/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/OrderListener.java new file mode 100644 index 0000000..12da68d --- /dev/null +++ b/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/OrderListener.java @@ -0,0 +1,18 @@ +package com.ankurm.kafka; + +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.List; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.KafkaHeaders; +import org.springframework.messaging.handler.annotation.Header; +import org.springframework.stereotype.Component; + +@Component +public class OrderListener { + public final List received = new CopyOnWriteArrayList<>(); + + @KafkaListener(topics = "orders") + void on(OrderEvent event, @Header(KafkaHeaders.RECEIVED_KEY) String key) { + received.add("key=" + key + " id=" + event.id() + " qty=" + event.qty()); + } +} diff --git a/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/SerdeProbe.java b/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/SerdeProbe.java new file mode 100644 index 0000000..f86587b --- /dev/null +++ b/kafka4-migration/migrated/src/main/java/com/ankurm/kafka/SerdeProbe.java @@ -0,0 +1,14 @@ +package com.ankurm.kafka; + +import java.nio.charset.StandardCharsets; +import org.springframework.kafka.support.serializer.JacksonJsonSerializer; + +/** Serialises one event with the JSON serializer, using only the runtime classpath (no test jars). */ +public class SerdeProbe { + public static void main(String[] args) { + try (JacksonJsonSerializer s = new JacksonJsonSerializer<>()) { + byte[] bytes = s.serialize("orders", new OrderEvent("o-1", 3)); + System.out.println("SERIALIZED " + new String(bytes, StandardCharsets.UTF_8)); + } + } +} diff --git a/kafka4-migration/migrated/src/main/resources/application.properties b/kafka4-migration/migrated/src/main/resources/application.properties new file mode 100644 index 0000000..027ca68 --- /dev/null +++ b/kafka4-migration/migrated/src/main/resources/application.properties @@ -0,0 +1,8 @@ +spring.kafka.consumer.group-id=orders-group +spring.kafka.consumer.auto-offset-reset=earliest +spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer +spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JacksonJsonSerializer +spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer +spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JacksonJsonDeserializer +spring.kafka.consumer.properties.spring.json.trusted.packages=com.ankurm.kafka +spring.kafka.consumer.properties.spring.json.value.default.type=com.ankurm.kafka.OrderEvent diff --git a/kafka4-migration/migrated/src/test/java/com/ankurm/kafka/OrderFlowTest.java b/kafka4-migration/migrated/src/test/java/com/ankurm/kafka/OrderFlowTest.java new file mode 100644 index 0000000..5f878ea --- /dev/null +++ b/kafka4-migration/migrated/src/test/java/com/ankurm/kafka/OrderFlowTest.java @@ -0,0 +1,27 @@ +package com.ankurm.kafka; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.test.EmbeddedKafkaBroker; +import org.springframework.kafka.test.context.EmbeddedKafka; + +@SpringBootTest(properties = "spring.kafka.bootstrap-servers=${spring.embedded.kafka.brokers}") +@EmbeddedKafka(topics = "orders", partitions = 1) +class OrderFlowTest { + @Autowired KafkaTemplate template; + @Autowired OrderListener listener; + @Autowired EmbeddedKafkaBroker broker; + + @Test + void sendAndReceive() { + template.send("orders", "k1", new OrderEvent("o-1", 3)); + await().untilAsserted(() -> assertThat(listener.received).hasSize(1)); + System.out.println("RESULT " + listener.received.get(0)); + System.out.println("BROKERS " + broker.getBrokersAsString().replaceAll(":\\d+", ":PORT")); + } +} diff --git a/kafka4-migration/output/01-legacy-kafka3.txt b/kafka4-migration/output/01-legacy-kafka3.txt new file mode 100644 index 0000000..6c8cd5b --- /dev/null +++ b/kafka4-migration/output/01-legacy-kafka3.txt @@ -0,0 +1,12 @@ +$ mvn test # Boot 3.5.16, JDK 21 +RESULT key=k1 id=o-1 qty=3 +BROKERS 127.0.0.1:PORT +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 7.853 s -- in com.ankurm.kafka.OrderFlowTest +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 +[INFO] BUILD SUCCESS + +org.apache.kafka:kafka-clients:jar:3.9.2:compile +org.springframework.kafka:spring-kafka:jar:3.3.16:compile + +$ java -cp target/classes: SerdeProbe +SERIALIZED {"id":"o-1","qty":3} diff --git a/kafka4-migration/output/02-boot4-no-starter.txt b/kafka4-migration/output/02-boot4-no-starter.txt new file mode 100644 index 0000000..a79ac2e --- /dev/null +++ b/kafka4-migration/output/02-boot4-no-starter.txt @@ -0,0 +1,5 @@ +$ mvn test # only the Boot version changed to 4.1.1 +[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 5.728 s <<< FAILURE! -- in com.ankurm.kafka.OrderFlowTest +[ERROR] OrderFlowTest.sendAndReceive ? UnsatisfiedDependency Error creating bean with name 'com.ankurm.kafka.OrderFlowTest': Unsatisfied dependency expressed through field 'template': No qualifying bean of type 'org.springframework.kafka.core.KafkaTemplate' available: expected at +[ERROR] Tests run: 1, Failures: 0, Errors: 1, Skipped: 0 +[INFO] BUILD FAILURE diff --git a/kafka4-migration/output/03-starter-added-tests-pass.txt b/kafka4-migration/output/03-starter-added-tests-pass.txt new file mode 100644 index 0000000..a5f1866 --- /dev/null +++ b/kafka4-migration/output/03-starter-added-tests-pass.txt @@ -0,0 +1,15 @@ +$ mvn test # spring-kafka replaced by spring-boot-starter-kafka; nothing else changed +RESULT key=k1 id=o-1 qty=3 +BROKERS localhost:PORT +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 9.853 s -- in com.ankurm.kafka.OrderFlowTest +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 +[INFO] BUILD SUCCESS + +org.apache.kafka:kafka-clients:jar:4.2.1:compile +org.springframework.boot:spring-boot-kafka:jar:4.1.1:compile +org.springframework.kafka:spring-kafka:jar:4.1.1:compile + +$ mvn dependency:list | grep jackson # runtime scope, then test scope +runtime: tools.jackson.core:jackson-core:jar:3.1.5:compile +runtime: tools.jackson.core:jackson-databind:jar:3.1.5:compile +test-only: com.fasterxml.jackson.core:jackson-databind:jar:2.21.5:test diff --git a/kafka4-migration/output/04-runtime-classpath-has-no-jackson2.txt b/kafka4-migration/output/04-runtime-classpath-has-no-jackson2.txt new file mode 100644 index 0000000..ff1486c --- /dev/null +++ b/kafka4-migration/output/04-runtime-classpath-has-no-jackson2.txt @@ -0,0 +1,3 @@ +$ compile and run SerdeProbe on the runtime classpath only (JsonSerializer from Spring Kafka 4.1.1) +[ERROR] class file for com.fasterxml.jackson.core.type.TypeReference not found +[ERROR] s2/src/main/java/com/ankurm/kafka/SerdeProbe.java:[9,45] cannot access com.fasterxml.jackson.core.type.TypeReference diff --git a/kafka4-migration/output/05-migrated.txt b/kafka4-migration/output/05-migrated.txt new file mode 100644 index 0000000..baca172 --- /dev/null +++ b/kafka4-migration/output/05-migrated.txt @@ -0,0 +1,13 @@ +$ mvn test # final migrated project (JacksonJsonSerializer, spring-boot-starter-jackson) +RESULT key=k1 id=o-1 qty=3 +BROKERS localhost:PORT +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 8.478 s -- in com.ankurm.kafka.OrderFlowTest +[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0 +[INFO] BUILD SUCCESS + +org.apache.kafka:kafka-clients:jar:4.2.1:compile +org.springframework.boot:spring-boot-kafka:jar:4.1.1:compile +org.springframework.kafka:spring-kafka:jar:4.1.1:compile + +$ java -cp target/classes: SerdeProbe +SERIALIZED {"id":"o-1","qty":3} diff --git a/kafka4-migration/output/06-serializer-classes.txt b/kafka4-migration/output/06-serializer-classes.txt new file mode 100644 index 0000000..8f9db30 --- /dev/null +++ b/kafka4-migration/output/06-serializer-classes.txt @@ -0,0 +1,2 @@ +$ jar tf spring-kafka-4.1.1.jar | grep support/serializer/(Json|Jackson) +JacksonJsonDeserializer.class JacksonJsonSerde.class JacksonJsonSerializer.class JacksonJsonTypeResolver.class JsonDeserializer.class JsonSerde.class JsonSerializer.class JsonTypeResolver.class diff --git a/kafka4-migration/scripts/run-all.sh b/kafka4-migration/scripts/run-all.sh new file mode 100755 index 0000000..76ac915 --- /dev/null +++ b/kafka4-migration/scripts/run-all.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Regenerates output/*.txt. Needs Maven, JDK 21 (J21), JDK 25 (J25) and Maven Central access. +set -u +HERE=$(cd "$(dirname "$0")/.." && pwd); OUT=$HERE/output; W=${WORK:-/tmp/k4-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 "$@" 2>&1 | grep -v JAVA_TOOL_OPTIONS | sed "s#$W/##g"); } +keep='^(RESULT|BROKERS)|Tests run:.*Fail|BUILD|ERROR.*(cannot access|No qualifying)' +stage() { rm -rf "$W/$1"; cp -r "$HERE/$2" "$W/$1"; rm -rf "$W/$1/target"; } +probe() { # probe : compile and run SerdeProbe on the RUNTIME classpath only + local jh=$1 d=$2 + (cd "$d" && JAVA_HOME=$jh $MVN -B -q compile dependency:build-classpath -Dmdep.includeScope=runtime -Dmdep.outputFile="$W/cp.txt" 2>&1 | grep -v JAVA_TOOL_OPTIONS | grep -E 'ERROR.*(cannot access|class file)' | sed "s#$W/##g" | sort -u + [ -f "$W/cp.txt" ] && JAVA_HOME=$jh $jh/bin/java -cp target/classes:$(cat "$W/cp.txt") com.ankurm.kafka.SerdeProbe 2>&1 | grep -v JAVA_TOOL_OPTIONS | head -3); rm -f "$W/cp.txt"; } +versions() { (cd "$2" && JAVA_HOME=$1 $MVN -B dependency:list 2>&1 | grep -E 'org.springframework.kafka:spring-kafka:|org.springframework.boot:spring-boot-kafka:|org.apache.kafka:kafka-clients:jar:[0-9]' | sed 's/^\[INFO\] *//; s/ -- module.*//' | sort -u); } + +stage s0 legacy +{ echo '$ mvn test # Boot 3.5.16, JDK 21'; mv_ "$J21" "$W/s0" test | grep -E "$keep" + echo; versions "$J21" "$W/s0" + echo; echo '$ java -cp target/classes: SerdeProbe'; probe "$J21" "$W/s0"; } > "$OUT/01-legacy-kafka3.txt" + +stage s1 legacy; rm "$W/s1/src/main/java/com/ankurm/kafka/SerdeProbe.java"; sed -i 's/3.5.16/4.1.1/; s/21/25/' "$W/s1/pom.xml" +{ echo '$ mvn test # only the Boot version changed to 4.1.1'; mv_ "$J25" "$W/s1" test | grep -E "$keep" | cut -c1-330; } > "$OUT/02-boot4-no-starter.txt" + +cp -r "$W/s1" "$W/s2"; rm -rf "$W/s2/target" +sed -i 's#org.springframework.kafkaspring-kafka#org.springframework.bootspring-boot-starter-kafka#' "$W/s2/pom.xml" +{ echo '$ mvn test # spring-kafka replaced by spring-boot-starter-kafka; nothing else changed'; mv_ "$J25" "$W/s2" test | grep -E "$keep" + echo; versions "$J25" "$W/s2" + echo; echo '$ mvn dependency:list | grep jackson # runtime scope, then test scope' + (cd "$W/s2" && JAVA_HOME=$J25 $MVN -B dependency:list -DincludeScope=runtime 2>&1 | grep -E 'jackson-(core|databind)' | sed 's/^\[INFO\] *//; s/ -- module.*//' | sort -u | sed 's/^/runtime: /' + JAVA_HOME=$J25 $MVN -B dependency:list -DexcludeScope=runtime 2>&1 | grep -E 'com.fasterxml.jackson.core:jackson-databind' | sed 's/^\[INFO\] *//; s/ -- module.*//' | sort -u | sed 's/^/test-only: /'); } > "$OUT/03-starter-added-tests-pass.txt" + +cp "$HERE/legacy/src/main/java/com/ankurm/kafka/SerdeProbe.java" "$W/s2/src/main/java/com/ankurm/kafka/" +{ echo '$ compile and run SerdeProbe on the runtime classpath only (JsonSerializer from Spring Kafka 4.1.1)'; probe "$J25" "$W/s2"; } > "$OUT/04-runtime-classpath-has-no-jackson2.txt" + +stage m migrated +{ echo '$ mvn test # final migrated project (JacksonJsonSerializer, spring-boot-starter-jackson)'; mv_ "$J25" "$W/m" test | grep -E "$keep" + echo; versions "$J25" "$W/m" + echo; echo '$ java -cp target/classes: SerdeProbe'; probe "$J25" "$W/m"; } > "$OUT/05-migrated.txt" + +# 06 API facts from the Spring Kafka 4.1.1 jar +J=$HOME/.m2/repository/org/springframework/kafka/spring-kafka/4.1.1/spring-kafka-4.1.1.jar +{ echo '$ jar tf spring-kafka-4.1.1.jar | grep support/serializer/(Json|Jackson)' + env -u JAVA_TOOL_OPTIONS $J25/bin/jar tf "$J" | grep -E 'support/serializer/(Json|Jackson)' | sed 's#.*/##' | sort | tr '\n' ' '; echo; } > "$OUT/06-serializer-classes.txt" +echo done