Skip to content

Migrate to Spring Boot 4, Jackson 3, and modern build tooling - #1

Open
v3rm0n wants to merge 7 commits into
masterfrom
spring-boot-4
Open

Migrate to Spring Boot 4, Jackson 3, and modern build tooling#1
v3rm0n wants to merge 7 commits into
masterfrom
spring-boot-4

Conversation

@v3rm0n

@v3rm0n v3rm0n commented May 29, 2026

Copy link
Copy Markdown

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

Builds on the existing Upgrade Gradle commit already on this branch.

Changes

Build tooling

  • Shadow plugin johnrengelman 8.1.1gradleup 9.4.2 (the old fork calls the removed FileCopyDetails.mode on Gradle 9); rewrote the protobuf dependency filter for the new API.
  • SpotBugs plugin 6.0.26.5.5, pinned tool 4.9.8 so it can read Java 25 class files (major version 69).
  • Pinned the Checkstyle tool to 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 the sendMessage overloads in ITransactionalKafkaMessageSender had to move next to each other (OverloadMethodsDeclarationOrder). Pinning keeps rule changes a deliberate edit rather than a side effect of a build tool bump.
  • Pinned a Temurin 25 toolchain (plus mise.toml and the foojay resolver) so main compilation no longer depends on whichever JDK happens to run Gradle.
  • Compile with --release rather than only -source/-target. With a JDK 25 toolchain, -target 17 pins the bytecode version but still lets javac link against JDK 25 APIs; --release makes the API surface match what we publish for. Tests continue to compile and run on Java 17.

Spring Boot 4

  • Added spring-boot-kafka (KafkaAutoConfiguration was split out of spring-boot-autoconfigure).
  • Updated Flyway/Validation autoconfigure imports to their new split-module packages (org.springframework.boot.flyway.autoconfigure.*, org.springframework.boot.validation.autoconfigure.*).
  • spring-boot-flyway and spring-boot-validation are compileOnly. They exist only to satisfy the FlywayAutoConfiguration/ValidationAutoConfiguration references in @AutoConfigureAfter, which Spring resolves from bytecode metadata and tolerates being absent. As implementation they would put flyway-core on the runtime classpath and in the published POM, so Flyway auto-configuration would activate in every consuming service and create flyway_schema_history in databases that never opted into Flyway. The test application and the demoapp do run migrations, so they declare spring-boot-flyway directly.

Jackson 3

  • Switched jackson-databind from com.fasterxml.jackson.core to tools.jackson.core and migrated every ObjectMapper import to tools.jackson.databind. JsonMapper extends ObjectMapper, so the call sites (writeValueAsBytes, readValue) are unchanged.
  • This is a breaking change for consumers: tw-tkms-starter now requires Jackson 3 on the runtime classpath.

Dependencies

  • lz4-java moved from the abandoned org.lz4 coordinate to the maintained at.yawk.lz4 fork (1.10.1). This is not a free choice — kafka-clients 4.1.2 already depends on at.yawk.lz4:lz4-java:1.10.1, and both artifacts ship the same net.jpountz.lz4 packages, so keeping org.lz4:lz4-java:1.8.0 would put duplicate classes on the classpath with no version conflict for Gradle to resolve.

SpotBugs findings

  • Removed three now-useless @SuppressFBWarnings (rules no longer triggered).
  • Fixed a real concurrency bug: TkmsRegisteredMessagesCollector.enabled is now volatile.
  • Broadened the generated-proto exclusion to the StoredMessage inner classes.

Test infrastructure

  • Replaced EOL wurstmeister/kafka:2.12-2.4.1 (its docker-in-docker port trick breaks on the current Docker API) with bitnamilegacy/kafka:3.6.0 using static port mappings.
  • KafkaConfiguration.createTopic now 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

  • Build matrix reduced to Spring Boot 4.0.6; 3.4.6/3.5.0 can no longer resolve the SB4-only artifacts or Jackson 3.
  • Kafka service containers moved to bitnamilegacy/kafka:3.6.0 with explicit KAFKA_CFG_ADVERTISED_LISTENERS (wurstmeister auto-derived them).
  • Dropped the gradle-version: 8.5 pin on the Setup Gradle steps. The wrapper (9.5.1) is what actually executes every arguments: step, so 8.5 was provisioned and discarded on every run.

Distribution

  • Version becomes 0.36.0-ly.1. Upstream has already tagged v0.36.0 (never published — Central stops at 0.35.0), so a plain 0.36.0 would 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.
  • Added a manually-triggered Publish workflow that pushes both tw-tkms-starter and tw-tkms-test-starter to GitHub Packages. The existing publishing config already reads MAVEN_URL/MAVEN_USER/MAVEN_PASSWORD, so this needed no build changes — just a workflow supplying them from the built-in GITHUB_TOKEN. It runs publishAllPublicationsToMavenRepository, not publish, which would also attempt Sonatype and fail on absent SONATYPE_* credentials.
  • Manual trigger is deliberate: 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.

Verification

./gradlew build --rerun-tasks passes: 113 tests, 0 failures, 3 skipped, plus SpotBugs, Checkstyle, Shadow jar and publishToMavenLocal.

Confirmed the dependency-scope change end to end: flyway-core is gone from :tw-tkms-starter:runtimeClasspath and from the generated POM, while the test suite still exercises Flyway through FlywayAutoConfiguration. Also verified javac receives --release 17 and 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.1 under com/transferwise/kafka/, with basic-auth credentials applied to every request. The uploaded POM carries tools.jackson.core, at.yawk.lz4 and spring-boot-starter-validation, and no Flyway.

Consumer migration

Backend already depends on com.transferwise.kafka:tw-tkms-starter and tw-tkms-test-starter, pinned to 0.35.0 via io.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 a read:packages token) and bump the pinned version to 0.36.0-ly.1.

Reviewer notes

  • This targets lightyeardev:master (the fork), not upstream transferwise.
  • Local runs need the Colima VM at ≥6 GiB (the default 2 GiB OOM-kills a Kafka broker mid-suite); not a repo change, just an env note.

Known test-infrastructure rough edges (pre-existing, not addressed here)

  • dockerCompose.stopContainers = false means the Kafka containers survive between runs, and a restarted bitnamilegacy/kafka container exits immediately without logging. A stale container from an earlier run makes the whole suite fail with TimeoutException ... Call: deleteTopics; ./gradlew composeDownForced clears it.
  • waitForTcpPorts = false means 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 official apache/kafka images would also mean dropping ZooKeeper for KRaft — worth doing separately.
  • The compose Kafka brokers now bind host ports 9092/9093 statically (the advertised listener has to name a port known up front), so a local Kafka on either port will block the suite. Everything else in the compose file still uses dynamic ports.

🤖 Generated with Claude Code

v3rm0n and others added 2 commits May 29, 2026 09:26
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
v3rm0n marked this pull request as ready for review May 29, 2026 13:15
v3rm0n and others added 2 commits May 29, 2026 17:07
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
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>
v3rm0n and others added 2 commits August 5, 2026 10:22
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants