Migrate to Spring Boot 4, Jackson 3, and modern build tooling - #1
Open
v3rm0n wants to merge 7 commits into
Open
Conversation
Complete the Spring Boot 4 migration and get the full build (compile, SpotBugs, Checkstyle, Shadow jar, integration tests) passing on Gradle 9 / Java 25. Build tooling: - Shadow plugin johnrengelman 8.1.1 -> gradleup 9.4.2 (Gradle 9 compat), rewrite the protobuf dependency filter for the new API. - SpotBugs plugin 6.0.2 -> 6.5.5, pin tool 4.9.8 so it can read Java 25 class files (major version 69). - Extend the Java 17 source/target compatibility branch to Spring Boot 4.x. Spring Boot 4: - Add spring-boot-kafka (KafkaAutoConfiguration split out of core). - Update Flyway/Validation autoconfigure imports to their new split-module packages. Jackson 3: - Switch jackson-databind coordinate from com.fasterxml.jackson.core to tools.jackson.core and migrate every ObjectMapper import to tools.jackson.databind. JsonMapper extends ObjectMapper, so call sites are unchanged. SpotBugs findings: - Remove three useless @SuppressFBWarnings (now-undetected rules). - Fix a real concurrency bug: make TkmsRegisteredMessagesCollector.enabled volatile. - Broaden the generated-proto exclusion to the StoredMessage inner classes. Test infrastructure: - Replace EOL wurstmeister/kafka:2.12-2.4.1 (broke on modern Docker API) with bitnamilegacy/kafka:3.6.0 using static port mappings. - Make KafkaConfiguration.createTopic wait until topic metadata has propagated (all partitions have a leader) before returning, fixing a startup topic-validation race that flaked under full-suite load. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
v3rm0n
marked this pull request as ready for review
May 29, 2026 13:15
Drop the exact patch/build qualifier (temurin-25.0.2+10.0.LTS) so mise resolves the latest Temurin 25.x instead of being pinned to a single build that has to be bumped by hand. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The migration drops Spring Boot 3 support, so reduce the build matrix to Spring Boot 4.0.6 (3.4.6/3.5.0 can no longer resolve the SB4-only artifacts and Jackson 3). Replace the EOL wurstmeister/kafka:2.12-2.2.0 service containers with bitnamilegacy/kafka:3.6.0 so the Kafka 4.1.2 client from Spring Boot 4 has a compatible broker. Bitnami needs explicit advertised listeners (wurstmeister auto-derived them), so set KAFKA_CFG_ADVERTISED_LISTENERS to each service's network alias. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
alvarl
previously approved these changes
May 29, 2026
Review follow-ups on the Spring Boot 4 migration. `spring-boot-flyway` was added as `implementation`, so `flyway-core` landed on the runtime classpath and in the published POM. Every service using tw-tkms would then have Flyway auto-configuration activate against its DataSource, creating `flyway_schema_history` in databases that never opted into Flyway. Neither module is needed at runtime -- they only supply the `FlywayAutoConfiguration`/`ValidationAutoConfiguration` references in `@AutoConfigureAfter`, which Spring resolves from bytecode metadata and tolerates being absent -- so make both `compileOnly`. The test application and demoapp genuinely run migrations, so they declare `spring-boot-flyway` themselves. Main code compiled with the new JDK 25 toolchain while only setting `-source`/`-target` 17, which checks the bytecode version but not the API surface. Post-17 APIs would compile and then fail for consumers on Java 17. Use `--release` so javac links against the API we publish for. Also drop the Gradle 8.5 pin in CI (the wrapper's 9.5.1 is what actually runs the build, so 8.5 was downloaded and discarded every run) and the unused `google()` buildscript repository. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SpotBugs got an explicit `toolVersion` in this migration, but Checkstyle was left following the Gradle version's default. That default jumped with the Gradle 9 upgrade, which is why the `sendMessage` overloads suddenly had to be declared next to each other -- unrelated code churn triggered by a build tool bump. Pin it to the version the build currently resolves so rule changes are a deliberate edit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The existing publishing setup already reads MAVEN_URL/MAVEN_USER/ MAVEN_PASSWORD, so pointing it at GitHub Packages needs no build changes -- only a workflow that supplies them. Uses the built-in GITHUB_TOKEN with `packages: write`, and runs `publishAllPublicationsToMavenRepository` rather than `publish`, which would also try Sonatype and fail on absent SONATYPE_* credentials. Triggered manually. GitHub Packages rejects re-publishing an existing release version, so firing on every master push would fail after the first; and tags created by `tagRelease` cannot trigger workflows because they are pushed with GITHUB_TOKEN. Version becomes 0.36.0-ly.1. Upstream has already tagged v0.36.0 (though never published it to Central, which stops at 0.35.0), so a plain 0.36.0 risks two different jars under identical coordinates, resolved by repository order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Completes the Spring Boot 4 migration on this branch and gets the full build green — compile, SpotBugs, Checkstyle, Shadow jar, and all integration tests — on Gradle 9 / Java 25. As a deliberate part of the migration, Jackson is moved from 2.x to 3.x (
tools.jackson).Changes
Build tooling
johnrengelman 8.1.1→gradleup 9.4.2(the old fork calls the removedFileCopyDetails.modeon Gradle 9); rewrote the protobuf dependency filter for the new API.6.0.2→6.5.5, pinned tool4.9.8so it can read Java 25 class files (major version 69).10.24.0. It was following the Gradle version's default, so the Gradle 9 upgrade silently changed which rules are enforced — that is why thesendMessageoverloads inITransactionalKafkaMessageSenderhad to move next to each other (OverloadMethodsDeclarationOrder). Pinning keeps rule changes a deliberate edit rather than a side effect of a build tool bump.mise.tomland the foojay resolver) so main compilation no longer depends on whichever JDK happens to run Gradle.--releaserather than only-source/-target. With a JDK 25 toolchain,-target 17pins the bytecode version but still lets javac link against JDK 25 APIs;--releasemakes the API surface match what we publish for. Tests continue to compile and run on Java 17.Spring Boot 4
spring-boot-kafka(KafkaAutoConfigurationwas split out ofspring-boot-autoconfigure).org.springframework.boot.flyway.autoconfigure.*,org.springframework.boot.validation.autoconfigure.*).spring-boot-flywayandspring-boot-validationarecompileOnly. They exist only to satisfy theFlywayAutoConfiguration/ValidationAutoConfigurationreferences in@AutoConfigureAfter, which Spring resolves from bytecode metadata and tolerates being absent. Asimplementationthey would putflyway-coreon the runtime classpath and in the published POM, so Flyway auto-configuration would activate in every consuming service and createflyway_schema_historyin databases that never opted into Flyway. The test application and the demoapp do run migrations, so they declarespring-boot-flywaydirectly.Jackson 3
jackson-databindfromcom.fasterxml.jackson.coretotools.jackson.coreand migrated everyObjectMapperimport totools.jackson.databind.JsonMapper extends ObjectMapper, so the call sites (writeValueAsBytes,readValue) are unchanged.tw-tkms-starternow requires Jackson 3 on the runtime classpath.Dependencies
lz4-javamoved from the abandonedorg.lz4coordinate to the maintainedat.yawk.lz4fork (1.10.1). This is not a free choice —kafka-clients 4.1.2already depends onat.yawk.lz4:lz4-java:1.10.1, and both artifacts ship the samenet.jpountz.lz4packages, so keepingorg.lz4:lz4-java:1.8.0would put duplicate classes on the classpath with no version conflict for Gradle to resolve.SpotBugs findings
@SuppressFBWarnings(rules no longer triggered).TkmsRegisteredMessagesCollector.enabledis nowvolatile.StoredMessageinner classes.Test infrastructure
wurstmeister/kafka:2.12-2.4.1(its docker-in-docker port trick breaks on the current Docker API) withbitnamilegacy/kafka:3.6.0using static port mappings.KafkaConfiguration.createTopicnow waits until topic metadata has fully propagated (every partition has an elected leader) before returning, fixing a startup topic-validation race that flaked under full-suite load.CI
4.0.6;3.4.6/3.5.0can no longer resolve the SB4-only artifacts or Jackson 3.bitnamilegacy/kafka:3.6.0with explicitKAFKA_CFG_ADVERTISED_LISTENERS(wurstmeister auto-derived them).gradle-version: 8.5pin on the Setup Gradle steps. The wrapper (9.5.1) is what actually executes everyarguments:step, so 8.5 was provisioned and discarded on every run.Distribution
0.36.0-ly.1. Upstream has already taggedv0.36.0(never published — Central stops at0.35.0), so a plain0.36.0would risk two different jars under identical coordinates, resolved by repository order. Consumers pin the version explicitly, so the suffix only has to be unique, not sort above upstream.Publishworkflow that pushes bothtw-tkms-starterandtw-tkms-test-starterto GitHub Packages. The existing publishing config already readsMAVEN_URL/MAVEN_USER/MAVEN_PASSWORD, so this needed no build changes — just a workflow supplying them from the built-inGITHUB_TOKEN. It runspublishAllPublicationsToMavenRepository, notpublish, which would also attempt Sonatype and fail on absentSONATYPE_*credentials.tagReleasecannot trigger workflows because they are pushed withGITHUB_TOKEN.Verification
./gradlew build --rerun-taskspasses: 113 tests, 0 failures, 3 skipped, plus SpotBugs, Checkstyle, Shadow jar andpublishToMavenLocal.Confirmed the dependency-scope change end to end:
flyway-coreis gone from:tw-tkms-starter:runtimeClasspathand from the generated POM, while the test suite still exercises Flyway throughFlywayAutoConfiguration. Also verified javac receives--release 17and that the emitted class files are major version 61.Exercised the publish path against a local HTTP Maven stand-in: both modules upload jar + POM + sources + javadoc at
0.36.0-ly.1undercom/transferwise/kafka/, with basic-auth credentials applied to every request. The uploaded POM carriestools.jackson.core,at.yawk.lz4andspring-boot-starter-validation, and no Flyway.Consumer migration
Backend already depends on
com.transferwise.kafka:tw-tkms-starterandtw-tkms-test-starter, pinned to0.35.0viaio.spring.dependency-management, and is on Spring Boot 4.0.7 — so it can take this. To switch it over, add the GitHub Packages repository (Maven reads there require authentication even for public packages, so every dev machine and CI job needs aread:packagestoken) and bump the pinned version to0.36.0-ly.1.Reviewer notes
transferwise.Known test-infrastructure rough edges (pre-existing, not addressed here)
dockerCompose.stopContainers = falsemeans the Kafka containers survive between runs, and a restartedbitnamilegacy/kafkacontainer exits immediately without logging. A stale container from an earlier run makes the whole suite fail withTimeoutException ... Call: deleteTopics;./gradlew composeDownForcedclears it.waitForTcpPorts = falsemeans the first run against fresh containers can start before MariaDB accepts connections, failing every test through a single poisoned Spring context. Re-running once the containers are warm passes.bitnamilegacy/*images are an archived, no-longer-patched line, so this swaps one unmaintained image for another. Moving to the officialapache/kafkaimages would also mean dropping ZooKeeper for KRaft — worth doing separately.🤖 Generated with Claude Code