Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 

Repository files navigation

SpringBoot and Java Migration Guide

Overview

This guide provides step-by-step instructions for migrating JVM microservices to:

  • Java 25 (from Java 17)
  • SpringBoot 4.0 (from SpringBoot 3.x)

Migration Path

Step Description Guide
0 Add Integration Tests (CRITICAL!) 05-testing-validation.md
1 Java 17 → Java 21 01-java-upgrade.md
2 Java 21 → Java 25 01-java-upgrade.md
3 SpringBoot 3.4.x → SpringBoot 3.5.x 02-springboot-upgrade.md
4 SpringBoot 3.5.x → SpringBoot 4.0 02-springboot-upgrade.md

CRITICAL: Integration Tests First!

Before any migration, ensure you have comprehensive integration tests.

Without integration tests:

  • You can't verify the migration didn't break anything
  • Breaking changes in Java/Spring versions go undetected
  • Rollback becomes much harder

Create an integration tests PR FIRST, merge it, then rebase migration branches.

What to Test

  • Repository operations (database CRUD)
  • API endpoints (REST controllers)
  • Service initialization (Spring context)
  • Core business logic

Important: PR Strategy - Stacked PRs

Each migration step must be done in a separate PR using stacked branches. Do not combine multiple migration steps in a single PR.

Stacked Branch Structure

main
  └── chore/add-integration-tests    (PR #0: Add tests - MERGE FIRST!)
main (with tests merged)
  └── chore/java-21-migration        (PR #1: Java 17 → 21)
        └── chore/java-25-migration  (PR #2: Java 21 → 25)
              └── chore/springboot-3.5  (PR #3: SpringBoot 3.4 → 3.5)
                    └── chore/springboot-4.0  (PR #4: SpringBoot 3.5 → 4.0)

Workflow

  1. Create integration tests PR → Merge to main
  2. Rebase migration branches onto main (with tests)
  3. Merge migration PRs in order

Creating Stacked PRs

# Step 0: Create integration tests branch
git checkout main
git checkout -b chore/add-integration-tests
# Add tests, commit, push
git push -u origin chore/add-integration-tests
gh pr create --base main --title "test: add integration tests"
# MERGE THIS FIRST before continuing!

# After tests PR is merged:
git checkout main
git pull origin main

# Step 1: Create Java 21 branch from main (now has tests)
git checkout -b chore/java-21-migration
# Make changes, commit, push
git push -u origin chore/java-21-migration
gh pr create --base main --title "chore: migrate to Java 21"

# Step 2: Create Java 25 branch from Java 21 branch
git checkout -b chore/java-25-migration
# Make changes, commit, push
git push -u origin chore/java-25-migration
gh pr create --base chore/java-21-migration --title "chore: migrate to Java 25"

# Continue pattern for SpringBoot upgrades...

Merging Stacked PRs

Merge in order from bottom to top:

  1. Merge PR #1 (Java 21) into main
  2. Rebase PR #2 onto main, then merge
  3. Rebase PR #3 onto main, then merge
  4. Rebase PR #4 onto main, then merge

This ensures:

  • Easy rollback if issues are found
  • Clear git history
  • Proper CI/CD validation at each step

Prerequisites

Before starting migration:

  • Integration tests exist and pass (CRITICAL!)
  • VPN connected
  • Authenticated with your staging/cluster environment (per your org's process)
  • Authenticated with your artifact registry (per your org's process)
  • Docker running (for integration tests)
  • SDKMAN installed for Java version management

Quick Reference Commands

Java Version Management (SDKMAN)

# List available versions
sdk list java

# Install specific version
sdk install java 21.0.9-amzn
sdk install java 25.0.1-amzn

# Switch version
sdk use java 21.0.9-amzn

Build Commands

# Full build with tests
./gradlew clean build

# Unit tests only
./gradlew test

# Integration tests
./gradlew integrationTest

# Build with deprecation warnings
./gradlew build --warning-mode all

Git Commands for Stacked PRs

# Rebase after base branch is merged
git checkout chore/java-25-migration
git rebase origin/main  # or origin/chore/java-21-migration
git push --force-with-lease

Files Reference

File Description
01-java-upgrade.md Java upgrade steps
02-springboot-upgrade.md SpringBoot upgrade steps
03-gradle-upgrade.md Gradle version requirements
04-dependencies-checklist.md Dependency compatibility
05-testing-validation.md Testing and validation guide
99-migration-log.md Migration learnings log


Java Upgrade Guide

SDK Management

SDKMAN

# List available Java versions (use -amzn versions for AWS compatibility)
sdk list java

# Install Amazon Corretto versions (preferred for AWS compatibility)
sdk install java 21.0.9-amzn    # Java 21
sdk install java 25.0.1-amzn    # Java 25

# Switch Java version
sdk use java 21.0.9-amzn        # Temporary (current shell)
sdk default java 21.0.9-amzn    # Permanent default

Important: PR Strategy

Each migration step must be done in a separate PR:

  1. Java 17 → 21: Create PR, get review, merge
  2. Java 21 → 25: Create PR, get review, merge
  3. SpringBoot 3.4.x → 3.5.x: Create PR, get review, merge
  4. SpringBoot 3.5.x → 4.0: Create PR, get review, merge

PR titles must follow conventional commits format: chore: migrate to Java 25

This ensures:

  • Easier rollback if issues arise
  • Clear history of changes
  • Proper CI/CD validation at each step
  • Easier debugging of any regressions

Migration: Java 17 -> Java 21

Files to Modify

File Change
.java-version 17 -> 21
Dockerfile eclipse-temurin:17-jre-alpine -> eclipse-temurin:21-jre-alpine

Note: build.gradle reads from .java-version automatically, no changes needed there.

Step-by-Step

  1. Update .java-version

    21
    
  2. Update Dockerfile

    FROM public.ecr.aws/docker/library/eclipse-temurin:21-jre-alpine
  3. Install and use Java 21 locally

    sdk install java 21.0.5-amzn
    sdk use java 21.0.5-amzn
    java -version  # Verify
  4. Build and test

    ./gradlew clean build

Java 21 Key Changes

  • Virtual Threads (JEP 444) - Production ready
  • Sequenced Collections (JEP 431) - New interfaces
  • Record Patterns (JEP 440) - Enhanced pattern matching
  • String Templates (Preview) - Not recommended for production yet
  • Deprecations removed - Some APIs removed that were deprecated in 17

Common Issues

Issue Solution
--add-opens warnings Add JVM args or update libraries
Reflection access errors Update to Java 21 compatible library versions
SecurityManager usage Deprecated, plan removal

Migration: Java 21 -> Java 25

Files to Modify

File Change
.java-version 21 -> 25
Dockerfile eclipse-temurin:21-jre-alpine -> eclipse-temurin:25-jre-alpine
scripts/assemble.sh ./gradlew docker -> ./gradlew jibDockerBuild

Step-by-Step

  1. Update .java-version

    25
    
  2. Update Dockerfile (if not using Jib for everything)

    FROM public.ecr.aws/docker/library/eclipse-temurin:25-jre-alpine
  3. Install and use Java 25 locally

    sdk install java 25.0.1-amzn
    sdk use java 25.0.1-amzn
    java -version
  4. Build and test

    ./gradlew clean build

Java 25 Key Changes

  • Further preview features promoted to standard
  • Performance improvements
  • Check release notes for specific deprecation removals

CRITICAL: Gradle 9.x Required

Java 25 requires Gradle 9.x because Gradle 8.x doesn't support class file version 69.

This means you need to update all Gradle plugins to Gradle 9 compatible versions BEFORE migrating to Java 25.

Common Issues

Issue Solution
Unsupported class file major version 69 Upgrade to Gradle 9.3.1+
Lombok fails with NoSuchFieldException Update Lombok to 1.18.40+
com.palantir.docker fails Replace with Google Jib 3.5.2
Avro plugin JavaPluginConvention error Update to davidmc24 1.9.1
JaCoCo instrumentation error Update to JaCoCo 0.8.14+
ByteBuddy/Mockito Java 25 error Add -Dnet.bytebuddy.experimental=true to test JVM args
internal Gradle plugin JacocoPlugin error Remove plugin, add jacoco plugin directly
integrationTest shows NO-SOURCE Add explicit testClassesDirs and classpath config (see below)
CI Task 'docker' not found Update scripts/assemble.sh to use jibDockerBuild
PR title validation fails Use conventional commits format: chore: migrate to Java 25

Plugin Compatibility for Gradle 9

Plugin Before After
Gradle 7.6.4 9.3.1
Lombok 1.18.30 1.18.40
JaCoCo 0.8.9 0.8.14
com.palantir.docker 0.36.0 Replace with Jib 3.5.2
gradle-avro-plugin commercehub 0.22.0 davidmc24 1.9.1
docker-compose 0.17.12 0.17.20
internal/org-specific plugin (varies) Remove (add jacoco directly)

Jib Configuration (replacing Palantir Docker)

plugins {
    id 'com.google.cloud.tools.jib' version '3.5.2'
}

jib {
    from {
        image = 'public.ecr.aws/docker/library/eclipse-temurin:25-jre-alpine'
    }
    to {
        image = 'your-image-name'
        tags = ['latest']
    }
    container {
        jvmFlags = ['-Djava.security.egd=file:/dev/./urandom', '-XX:InitialRAMPercentage=70.0', '-XX:MaxRAMPercentage=70.0']
        ports = ['8080']
        mainClass = 'com.your.MainClass'
    }
}

Update scripts/assemble.sh for Jib

When replacing Palantir Docker with Jib, update the assemble script:

#!/bin/sh
# Before (Palantir Docker):
# ./gradlew docker --no-daemon -x check -x composeUp --info --gradle-user-home ${HOME}/.gradle

# After (Jib):
./gradlew jibDockerBuild --no-daemon -x check -x composeUp --info --gradle-user-home ${HOME}/.gradle

Integration Test Configuration for Gradle 9.x

CRITICAL: Gradle 9.x requires explicit test class directories and classpath configuration for custom Test tasks.

Without this fix, integrationTest will show NO-SOURCE and skip all integration tests.

Update testing.gradle:

task integrationTest(type: Test) {
    // Gradle 9.x requires explicit test class directories and classpath configuration
    testClassesDirs = sourceSets.test.output.classesDirs
    classpath = sourceSets.test.runtimeClasspath

    useJUnitPlatform {
        includeTags 'integration-test'
    }
    // ... rest of configuration
}

ByteBuddy Experimental Flag

Add to test configurations:

test {
    useJUnitPlatform()
    jvmArgs '-Dnet.bytebuddy.experimental=true'
}

integrationTest {
    jvmArgs '-Dnet.bytebuddy.experimental=true'
}

SpringBoot Upgrade Guide

Migration Path

SpringBoot 3.4.x -> 3.5.x -> 4.0

Important: Always upgrade incrementally. Do not skip versions.


SpringBoot 3.4.x -> 3.5.x

Files to Modify

File Change
build.gradle springBootVersion = '3.4.5' -> springBootVersion = '3.5.x'

Step-by-Step

  1. Update build.gradle

    springBootVersion = '3.5.0'  // Or latest 3.5.x
  2. Update Spring Cloud (if needed)

    springCloudVersion = '2025.0.0'  // Check compatibility
  3. Update Hibernate dialect (recommended before 4.0)

    # application.yml
    spring:
      jpa:
        properties:
          hibernate:
            dialect: org.hibernate.dialect.PostgreSQLDialect  # Not PostgreSQL95Dialect
  4. Build and test

    ./gradlew clean build

SpringBoot 3.5 Key Changes

Check: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.5-Release-Notes

  • Configuration property changes
  • Dependency upgrades
  • New features and deprecations

Common Issues

Issue Solution
Property renamed Check release notes for property mappings
Deprecated API Update to new API before 4.0
Hibernate dialect warning Use PostgreSQLDialect instead of versioned dialects

SpringBoot 3.5.x -> 4.0

Files to Modify

File Change
build.gradle springBootVersion = '3.5.x' -> springBootVersion = '4.0.x'
build.gradle Update springCloudVersion to 2025.1.x
build.gradle Update incompatible dependencies
Test files @MockBean -> @MockitoBean

Step-by-Step

  1. Update build.gradle

    springBootVersion = '4.0.2'
    springCloudVersion = '2025.1.1'  // NOT 2026.x - see note below

    IMPORTANT: Despite some documentation suggesting Spring Cloud 2026.x, Spring Boot 4.0.x works with Spring Cloud 2025.1.x. Check Spring Cloud Supported Versions

  2. Update modularized starters

    // Old (3.5.x)
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.springframework.boot:spring-boot-starter-batch'
    implementation 'org.flywaydb:flyway-core'
    
    // New (4.0.x)
    implementation 'org.springframework.boot:spring-boot-starter-aspectj'  // renamed
    implementation 'org.springframework.boot:spring-boot-starter-batch-jdbc'  // modularized
    implementation 'org.springframework.boot:spring-boot-starter-flyway'  // use starter
    
    // For @WebMvcTest in tests
    testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test'
  3. Update test annotations

    // Old (3.5.x)
    import org.springframework.boot.test.mock.mockito.MockBean;
    import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    
    // New (4.0.x)
    import org.springframework.test.context.bean.override.mockito.MockitoBean;
    import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest;
  4. Update package imports

    // DataSourceProperties moved
    // Old: org.springframework.boot.autoconfigure.jdbc.DataSourceProperties
    // New: org.springframework.boot.jdbc.autoconfigure.DataSourceProperties
    
    // Spring Batch infrastructure moved
    // Old: org.springframework.batch.item.*
    // New: org.springframework.batch.infrastructure.item.*
  5. Refactor Spring Batch code (if using custom readers)

    Spring Batch 6.0 removes setters from item readers. Must use builders:

    // BROKEN in Spring Batch 6.0
    JdbcCursorItemReader<T> reader = new JdbcCursorItemReader<>();
    reader.setDataSource(dataSource);
    reader.setSql(sql);
    
    // CORRECT for Spring Batch 6.0
    JdbcCursorItemReader<T> reader = new JdbcCursorItemReaderBuilder<T>()
        .dataSource(dataSource)
        .sql(sql)
        .rowMapper(rowMapper)
        .build();
  6. Build and test

    ./gradlew clean build

SpringBoot 4.0 Major Breaking Changes

Full guide: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide

Baseline Requirements

  • Java 17+ required (Java 25 recommended)
  • Gradle 8.10+ required (Gradle 9.x for Java 25)

Package Relocations

Old Package New Package
o.s.boot.test.mock.mockito.MockBean o.s.test.context.bean.override.mockito.MockitoBean
o.s.boot.test.autoconfigure.web.servlet.WebMvcTest o.s.boot.webmvc.test.autoconfigure.WebMvcTest
o.s.boot.autoconfigure.jdbc.DataSourceProperties o.s.boot.jdbc.autoconfigure.DataSourceProperties
o.s.batch.item.* o.s.batch.infrastructure.item.*

Starter Renames

Old Starter New Starter
spring-boot-starter-aop spring-boot-starter-aspectj
spring-boot-starter-batch spring-boot-starter-batch-jdbc (for JDBC)
flyway-core spring-boot-starter-flyway
(none) spring-boot-starter-webmvc-test (for @WebMvcTest)

Spring Batch 6.0 Changes

  • All item readers/writers use builder pattern only
  • Setters removed from JdbcCursorItemReader, JdbcPagingItemReader, etc.
  • Must refactor any code that extends these classes

Spring Cloud Compatibility

SpringBoot Spring Cloud Notes
3.4.x 2024.0.x Current stable
3.5.x 2025.0.x
4.0.x 2025.1.x Not 2026.x!

Note: Spring Cloud 2026.x does not exist yet as of Feb 2026. Spring Boot 4.0.x is compatible with Spring Cloud 2025.1.x.


Configuration Property Changes

Check for deprecated properties

# Run with deprecation warnings
./gradlew bootRun --args='--spring.config.additional-location=optional:file:./deprecated-check.yml'

Common property migrations

Old Property New Property
spring.dao.exceptiontranslation.enabled spring.persistence.exceptiontranslation.enabled
management.tracing.enabled management.tracing.export.enabled

Check the official migration guide for complete property renames.


Verification Checklist

After each upgrade:

  • ./gradlew clean build passes
  • Unit tests pass: ./gradlew test
  • Integration tests pass: ./gradlew integrationTest
  • Application starts locally
  • No deprecation warnings (or documented)
  • Health endpoint responds: /actuator/health
  • For data/ETL services: Compare output between versions (reset consumer offsets before each export)

Gradle Upgrade Guide

Version Requirements

Java Version Min Gradle Recommended
17 7.3+ 7.6.4
21 8.4+ 8.10+
25 8.10+ 8.12+

Current Gradle Version

Check current version:

./gradlew --version

Check wrapper properties:

cat gradle/wrapper/gradle-wrapper.properties

Upgrading Gradle

Step-by-Step

  1. Update wrapper

    ./gradlew wrapper --gradle-version=8.12
  2. Verify update

    ./gradlew --version
  3. Test build

    ./gradlew clean build

Alternative: Manual Update

Edit gradle/wrapper/gradle-wrapper.properties:

distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip

Then run:

./gradlew wrapper

Plugin Compatibility

Plugins to Check

Plugin Current Java 21 Java 25
org.springframework.boot 3.4.5 4.0+
io.spring.dependency-management 1.1.7 Check
com.palantir.docker 0.36.0 Check
com.avast.gradle.docker-compose 0.17.12 Check
org.sonarqube 7.2.0.6526 Check
org.flywaydb.flyway 11.8.2 Check
internal/org-specific plugin (varies) Check

Updating Plugins

In build.gradle plugins block:

plugins {
    id 'org.springframework.boot' version '4.0.0'
    // ... other plugins
}

Build Script Changes

Gradle 8.x Changes

  1. Configuration cache - May require code changes
  2. Build scan - Updated integration
  3. Java toolchain - Better support

Recommended: Use Java Toolchain

Instead of setting sourceCompatibility/targetCompatibility, use toolchains:

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

This ensures consistent Java version across different machines.


Common Issues

Issue: Plugin not compatible

Error: Plugin X requires Gradle Y

Solution: Update the plugin version in build.gradle

Issue: Deprecated API warnings

Error: Various deprecation warnings during build

Solution:

./gradlew build --warning-mode all

Then address each warning.

Issue: Configuration cache problems

Error: Configuration cache state could not be cached

Solution: Update plugins or disable configuration cache temporarily:

./gradlew build --no-configuration-cache

Verification

After Gradle upgrade:

# Verify version
./gradlew --version

# Clean build
./gradlew clean build

# Check for warnings
./gradlew build --warning-mode all

# Run tests
./gradlew test

Dependencies Compatibility Checklist

Current Dependencies (example service)

Spring Ecosystem

Dependency Current Java 21 Java 25 / SB 4.0 Notes
SpringBoot 3.4.5 4.0.x Major upgrade required
Spring Cloud 2024.0.1 2026.0.x Update with SpringBoot
Spring Dependency Management 1.1.7 Check May need update

Database

Dependency Current Java 21 Java 25 / SB 4.0 Notes
PostgreSQL Driver 42.7.5 Usually compatible
Flyway 11.8.2 Check May need update

Kafka/Messaging

Dependency Current Java 21 Java 25 / SB 4.0 Notes
Kafka Clients 2.6.0 ⚠️ ⚠️ Very old, update recommended
Kafka Avro Serde 5.3.0 ⚠️ ⚠️ Very old, check Confluent versions
Avro 1.12.0 Recent version
Spring Cloud AWS Messaging 2.2.6.RELEASE ⚠️ EOL, migrate to AWS SDK v2

Utilities

Dependency Current Java 21 Java 25 / SB 4.0 Notes
Lombok 1.18.30 Check Java 25 support TBD
Commons IO 2.19.0 Recent version
Commons Lang3 3.17.0 Recent version
Commons Collections4 4.5.0 Recent version

Logging

Dependency Current Java 21 Java 25 / SB 4.0 Notes
Logback 1.5.3 Managed by Spring
Logback Contrib 0.1.5 ⚠️ ⚠️ Old, check alternatives

Testing

Dependency Current Java 21 Java 25 / SB 4.0 Notes
JUnit Jupiter 5.11.3 Recent version
Mockito Managed Spring managed
Awaitility 4.3.0 Recent version
Hamcrest 3.0 Recent version

High-Risk Dependencies

1. Spring Cloud AWS Messaging (2.2.6.RELEASE)

Status: End of Life

Problem: Old version, not compatible with newer Spring Boot

Solution:

  • Migrate to io.awspring.cloud:spring-cloud-aws-starter-sqs (AWS SDK v2)
  • Or use software.amazon.awssdk:sqs
// Old
implementation 'org.springframework.cloud:spring-cloud-aws-messaging:2.2.6.RELEASE'

// New
implementation 'io.awspring.cloud:spring-cloud-aws-starter-sqs:3.x.x'

2. Kafka Clients (2.6.0)

Status: Very old (2020)

Problem: May have compatibility issues with newer Java

Solution: Update to 3.x:

kafkaClientVersion = '3.7.0'

3. Kafka Avro Serde (5.3.0)

Status: Very old

Problem: Tied to old Kafka version

Solution: Update to match Kafka clients version


Dependency Update Order

  1. Before Java 21:

    • Update Kafka clients to 3.x
    • Update Confluent dependencies
    • Consider Spring Cloud AWS migration
  2. Before SpringBoot 4.0:

    • Complete Spring Cloud AWS migration
    • Update all Spring Cloud dependencies
    • Verify Lombok Java 25 support

Checking Dependency Versions

# List all dependencies
./gradlew dependencies

# Check for updates (if using versions plugin)
./gradlew dependencyUpdates

# Check specific configuration
./gradlew dependencies --configuration runtimeClasspath

Known Incompatibilities

Dependency Issue Workaround
spring-cloud-aws-messaging Not maintained Migrate to awspring
logback-contrib Potentially unmaintained Check for alternatives
Kafka 2.x Old API Update to 3.x

Testing and Validation Guide

CRITICAL: Integration Tests Before Migration

Before starting ANY migration, ensure you have comprehensive integration tests that verify:

  1. Database operations - All repository methods work correctly
  2. API endpoints - REST controllers respond as expected
  3. Application startup - Spring context initializes properly
  4. Business logic - Core services function correctly

Why is this important?

  • Integration tests catch issues that unit tests miss
  • They verify the entire stack works together (DB → Service → API)
  • Breaking changes in Java/Spring versions often surface in integration tests
  • Without a test baseline, you can't verify the migration didn't break anything

Add integration tests PR BEFORE migration PRs, then rebase migration branches after tests are merged.


Pre-Migration Checklist

Before starting any migration:

  • Integration tests exist and pass (CRITICAL!)
  • VPN connected
  • Authenticated with your cluster environment (per your org's process)
  • Artifact registry access configured
  • Docker running
  • Clean working directory: git status
  • On feature branch
  • Correct Java version: java -version

Build Verification

Full Build

./gradlew clean build

This runs:

  1. Compilation
  2. Unit tests
  3. Integration tests (requires Docker)

Build with Warnings

./gradlew clean build --warning-mode all

Shows deprecation warnings - important for migration planning.


Test Execution

Unit Tests Only

./gradlew test

Note: Unit tests should NOT require Docker.

Integration Tests

./gradlew integrationTest

Requires: Docker running (for PostgreSQL container)

Integration tests are tagged with @Tag("integration-test") and run with docker-compose.

Force Re-Run (Skip Cache)

./gradlew integrationTest --rerun-tasks

All Tests with Details

./gradlew test integrationTest --info

Specific Test Class

./gradlew test --tests "com.example.service.SomeTest"
./gradlew integrationTest --tests "*RepositoryIT"

Test Structure

Unit Tests (test task)

  • Located in src/test/java
  • Class names: *Test.java
  • No Docker/external dependencies
  • Mock all external services

Integration Tests (integrationTest task)

  • Located in src/test/java
  • Class names: *IT.java
  • Tagged with @Tag("integration-test")
  • Use @SpringBootTest with @ActiveProfiles("test")
  • Require Docker for PostgreSQL

Test Configuration

testing.gradle:

test {
    useJUnitPlatform {
        excludeTags 'integration-test'
    }
    filter {
        excludeTestsMatching '*IT'  // Prevent Spring context init
    }
}

task integrationTest(type: Test) {
    useJUnitPlatform {
        includeTags 'integration-test'
    }
}

Test Troubleshooting

Docker Compose Issues

Problem: Integration tests fail with connection errors

Check:

# Docker running?
docker ps

# Port available?
lsof -i :5432

# Docker Compose works?
cd docker && docker compose up -d && docker compose down

Spring Context Fails to Load

Problem: Failed to load ApplicationContext

Common causes:

  1. Missing @MockBean for Kafka-related beans
  2. Database not available
  3. Missing test configuration

Solution: Check error log for the specific bean that failed.

Test Discovery Issues

Problem: Tests not found or wrong tests run

Check:

# See what tests will run
./gradlew test --dry-run
./gradlew integrationTest --dry-run

# Check test output location
ls -la build/test-results/test/
ls -la build/test-results/integrationTest/

Memory Issues

Problem: OutOfMemoryError during tests

Solution: In gradle.properties:

org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g

Application Startup Verification

Local Startup

./gradlew bootRun

With Profile

./gradlew bootRun --args='--spring.profiles.active=local'

Health Check

Once running, verify:

curl http://localhost:8080/actuator/health

Expected response:

{"status":"UP"}

Docker Build Verification

Build with Jib (Java 25+)

./gradlew jibDockerBuild

Build with Palantir (Java 17/21)

./gradlew docker

Verify Image

docker run your-image-name java -version

Post-Migration Validation Checklist

After EACH migration step:

Build & Tests

  • ./gradlew clean build - Passes
  • ./gradlew test - All unit tests pass
  • ./gradlew integrationTest - All integration tests pass

Application

  • ./gradlew bootRun - Starts without errors
  • /actuator/health - Returns UP
  • No unexpected deprecation warnings in logs

Docker

  • Docker image builds successfully
  • Container starts and runs

Code Quality

  • No new compiler warnings
  • ./gradlew build --warning-mode all - Review warnings

CI/CD

  • Push to branch
  • CI pipeline passes
  • All checks green before merging

Rollback Plan

If migration fails:

# Discard changes
git checkout .

# Or reset to last commit
git reset --hard HEAD

# Switch back Java version (using SDKMAN)
sdk use java 17.0.17-amzn

Performance Baseline

Before migration, record:

# Build time
time ./gradlew clean build

# Test time
time ./gradlew test
time ./gradlew integrationTest

# Startup time (check logs)
./gradlew bootRun

Compare after migration to detect regressions.


Migration Log

This is a living document recording learnings from each repository migration.


Template

## [Repo Name] - [Date]

### Migration: [From] -> [To]

**Duration:** X hours

### Changes Made
-

### Issues Encountered
| Issue | Error Message | Solution | Time Spent |
|-------|--------------|----------|------------|
| | | | |

### Commands Run
\`\`\`bash
# Commands here
\`\`\`

### Notes for Next Migration
-

---

Example service - 2026-02-03

Migration: Java 17 -> Java 21

Duration: ~30 minutes (including CI fix)

Status: SUCCESS (merged)

Changes Made

  • .java-version: 17 -> 21
  • Dockerfile: eclipse-temurin:17-jre-alpine -> eclipse-temurin:21-jre-alpine
  • gradle-wrapper.properties: Gradle 7.6.4 -> 8.10.2 (required for Java 21)
  • .github/workflows/pr.yaml: java_version: '17' -> '21'
  • .github/workflows/main.yaml: java_version: '17' -> '21' (3 places)

Issues Encountered

Issue Error Message Solution Time Spent
Gradle 7.6.4 doesn't support Java 21 Unsupported class file major version 65 Upgrade to Gradle 8.10.2 5 min
CI still using Java 17 Tests failing with version mismatch Update hardcoded java_version in workflows 10 min
Deprecated bootJar properties Could not get unknown property 'archiveName' Use archiveFile and archiveFileName.get() 5 min

Commands Run

# Install and switch to Java 21
sdk install java 21.0.9-amzn
sdk use java 21.0.9-amzn
java -version

# Build and test
./gradlew clean build

Build Output

  • All 19 unit tests passed
  • 1 integration test passed (1 skipped)
  • Build successful in 23s

Notes for Next Migration

  • Always check CI workflow files for hardcoded Java versions
  • Gradle 8.x is required for Java 21+ (major version 65)
  • Check for deprecated Gradle API usage (archiveName -> archiveFileName.get())

Example service - 2026-02-04

Migration: Java 21 -> Java 25

Duration: ~2 hours (including multiple CI fixes)

Status: SUCCESS (merged)

Changes Made

  • .java-version: 21 -> 25
  • Dockerfile: eclipse-temurin:21-jre-alpine -> eclipse-temurin:25-jre-alpine
  • gradle-wrapper.properties: Gradle 8.10.2 -> 9.3.1
  • build.gradle:
    • Lombok: 1.18.30 -> 1.18.40
    • JaCoCo: 0.8.9 -> 0.8.14
    • Avro plugin: com.commercehub 0.22.0 -> com.github.davidmc24 1.9.1
    • Docker plugin: com.palantir.docker -> com.google.cloud.tools.jib 3.5.2
    • docker-compose: 0.17.12 -> 0.17.20
    • Removed: internal/org-specific Gradle plugin (incompatible with Gradle 9)
    • Added: jacoco plugin directly
    • Added: ByteBuddy experimental flag for tests
  • SomeEntity.avsc: Fixed malformed logicalType placement
  • SomeEntityMapper.java: Updated to use List
  • SomeEntityMapperTest.java: Updated assertions for UUID
  • testing.gradle:
    • Added ByteBuddy experimental flag for integration tests
    • Added explicit testClassesDirs and classpath for Gradle 9.x compatibility
  • scripts/assemble.sh: Changed ./gradlew docker to ./gradlew jibDockerBuild
  • .github/workflows/pr.yaml: java_version: '21' -> '25'
  • .github/workflows/main.yaml: java_version: '21' -> '25' (3 places)

Issues Encountered

Issue Error Message Solution Time Spent
Lombok incompatibility NoSuchFieldException: com.sun.tools.javac.code.TypeTag Update Lombok to 1.18.40 5 min
Gradle 8.x doesn't support Java 25 Unsupported class file major version 69 Upgrade to Gradle 9.3.1 5 min
Palantir Docker plugin incompatible Could not create plugin PalantirDockerPlugin Replace with Google Jib 10 min
Internal Gradle plugin incompatible JacocoPlugin closure invalid Remove plugin, add jacoco directly 5 min
Avro plugin incompatible org/gradle/api/plugins/JavaPluginConvention Update to 1.9.1 (new coordinates) 5 min
Malformed Avro schema List<String> cannot be converted to UUID Fix logicalType placement in schema 10 min
JaCoCo 0.8.9 doesn't support Java 25 Error while instrumenting Update to 0.8.14 2 min
ByteBuddy/Mockito Java 25 Java 25 (69) is not supported by Byte Buddy Add -Dnet.bytebuddy.experimental=true 3 min
CI build-docker-image fails Task 'docker' not found in root project Update scripts/assemble.sh to use jibDockerBuild 5 min
PR title validation fails No release type found in pull request title Use conventional commits format: chore: migrate to Java 25 2 min
Integration tests show NO-SOURCE Skipping task ':integrationTest' as it has no source files Add explicit testClassesDirs and classpath in testing.gradle 15 min

Plugin Updates Summary

Plugin Before After Notes
Gradle 8.10.2 9.3.1 Required for Java 25
Lombok 1.18.30 1.18.40 Java 25 support
JaCoCo 0.8.9 0.8.14 Java 25 support
Avro plugin commercehub 0.22.0 davidmc24 1.9.1 Gradle 9 support
Docker palantir 0.36.0 jib 3.5.2 Replaced unmaintained plugin
docker-compose 0.17.12 0.17.20 Gradle 9 support
internal/org-specific plugin (varies) removed Not compatible with Gradle 9

Critical Fix: Integration Tests in Gradle 9.x

Gradle 9.x requires explicit configuration for custom Test tasks. Without this, integration tests silently skip with NO-SOURCE.

task integrationTest(type: Test) {
    // Gradle 9.x requires explicit test class directories and classpath configuration
    testClassesDirs = sourceSets.test.output.classesDirs
    classpath = sourceSets.test.runtimeClasspath

    useJUnitPlatform {
        includeTags 'integration-test'
    }
    // ... rest of configuration
}

Critical Fix: CI Docker Build Script

When replacing Palantir Docker with Jib, remember to update build scripts:

# scripts/assemble.sh - Before:
./gradlew docker --no-daemon -x check -x composeUp --info --gradle-user-home ${HOME}/.gradle

# scripts/assemble.sh - After:
./gradlew jibDockerBuild --no-daemon -x check -x composeUp --info --gradle-user-home ${HOME}/.gradle

Commands Run

sdk install java 25.0.1-amzn
sdk use java 25.0.1-amzn
./gradlew clean build          # SUCCESS - All 18 unit tests pass
./gradlew integrationTest      # SUCCESS - All 19 integration tests pass (after fix)

Notes for Next Migration

  • Java 25 requires Gradle 9.x (major version 69)
  • Jib is a good replacement for Palantir Docker - no Docker daemon needed
  • CRITICAL: Update scripts/assemble.sh when replacing Docker plugin with Jib
  • CRITICAL: Add testClassesDirs and classpath to custom Test tasks for Gradle 9.x
  • Watch for malformed Avro schemas - old plugins may have hidden issues
  • ByteBuddy experimental flag needed until official Java 25 support
  • PR titles must follow conventional commits format (e.g., chore: migrate to Java 25)

Example service - 2026-02-05

Migration: SpringBoot 3.4.5 -> 3.5.0

Duration: ~1 hour

Status: SUCCESS (merged)

Changes Made

  • build.gradle: springBootVersion 3.4.5 -> 3.5.0
  • build.gradle: springCloudVersion 2024.0.0 -> 2025.0.0
  • application.yml: Hibernate dialect PostgreSQL95Dialect -> PostgreSQLDialect

Issues Encountered

Issue Error Message Solution Time Spent
Hibernate dialect deprecated Warning about deprecated dialect Update to PostgreSQLDialect 5 min

Notes for Next Migration

  • Spring Boot 3.5 is a smooth upgrade from 3.4
  • Hibernate dialect should be updated before 4.0

Example service - 2026-02-05

Migration: SpringBoot 3.5.0 -> 4.0.2

Duration: ~3 hours (including data-parity validation)

Status: SUCCESS

Changes Made

  • build.gradle:
    • springBootVersion: 3.5.0 -> 4.0.2
    • springCloudVersion: 2025.0.0 -> 2025.1.1 (NOT 2026.x as some docs suggest)
    • spring-boot-starter-aop -> spring-boot-starter-aspectj (renamed)
    • spring-boot-starter-batch -> spring-boot-starter-batch-jdbc (modularized)
    • flyway-core -> spring-boot-starter-flyway (required for proper integration)
    • Added: spring-boot-starter-webmvc-test (for @WebMvcTest)
    • Removed explicit JUnit version pins (managed by Spring Boot)
  • DataSourceConfig.java: Package relocation for DataSourceProperties
  • NamedParameterJdbcCursorItemReader.java: Complete rewrite from class to factory pattern
  • EtlPipeline.java, JobDataController.java: Updated Spring Batch imports
  • All integration tests: @MockBean -> @MockitoBean, @WebMvcTest package change

Issues Encountered

Issue Error Message Solution Time Spent
spring-boot-starter-aop not found Dependency resolution failure Renamed to spring-boot-starter-aspectj 5 min
DataSourceProperties not found Package does not exist Moved to o.s.boot.jdbc.autoconfigure 5 min
Spring Batch packages not found ExecutionContext, JdbcCursorItemReader Moved to o.s.batch.infrastructure.* 10 min
JdbcCursorItemReader setters removed Spring Batch 6.0 uses builder-only pattern Refactor to factory class with builder 30 min
@WebMvcTest not found Package relocation o.s.boot.webmvc.test.autoconfigure 5 min
@MockBean not found Replaced by @MockitoBean o.s.test.context.bean.override.mockito 10 min
JUnit NoSuchMethodError Version conflict with Spring Boot managed versions Remove explicit version pins 15 min
WebMvcTest dependency missing Class not found at runtime Add spring-boot-starter-webmvc-test 5 min
Flyway not running at startup Migrations skipped Use spring-boot-starter-flyway instead of flyway-core 10 min

Critical: Spring Batch 6.0 Builder-Only Pattern

Spring Batch 6.0 removes setters from item readers. You cannot extend JdbcCursorItemReader and override methods. Must use builders:

// BEFORE (Spring Batch 5.x) - BROKEN in 6.0
public class NamedParameterJdbcCursorItemReader<T> extends JdbcCursorItemReader<T> {
    @Override
    public void setSql(String sql) {
        super.setSql(NamedParameterUtils.substituteNamedParameters(sql, params));
    }
}

// AFTER (Spring Batch 6.0) - Factory pattern with builder
public final class NamedParameterJdbcCursorItemReader {
    public static <T> JdbcCursorItemReader<T> create(
            DataSource dataSource, String sql, 
            SqlParameterSource params, RowMapper<T> rowMapper) {
        String convertedSql = NamedParameterUtils.substituteNamedParameters(sql, params);
        return new JdbcCursorItemReaderBuilder<T>()
                .dataSource(dataSource)
                .sql(convertedSql)
                .rowMapper(rowMapper)
                .preparedStatementSetter(createPss(sql, params))
                .saveState(false)
                .build();
    }
}

Critical: Spring Cloud Version for Spring Boot 4.0

The migration guide incorrectly states Spring Cloud 2026.x - use 2025.1.x instead:

springBootVersion = '4.0.2'
springCloudVersion = '2025.1.1'  // NOT 2026.x

Critical: Test Annotation Changes

Old (Spring Boot 3.x) New (Spring Boot 4.0)
@MockBean @MockitoBean
o.s.boot.test.autoconfigure.web.servlet.WebMvcTest o.s.boot.webmvc.test.autoconfigure.WebMvcTest
o.s.boot.autoconfigure.jdbc.DataSourceProperties o.s.boot.jdbc.autoconfigure.DataSourceProperties
o.s.batch.item.* o.s.batch.infrastructure.item.*

ETL Validation

Validated migration by comparing ETL exports between Spring Boot 3.5 and 4.0.2:

  • All 6 topics produced identical output (20,376 records total)
  • Comparison requires resetting Kafka consumer offsets before each branch export

Commands Run

./gradlew clean build          # SUCCESS
./gradlew test                 # SUCCESS - 18 unit tests
./gradlew integrationTest      # SUCCESS - 19 integration tests
./gradlew <yourDataExportTask> -PexportName=springboot4 -Pstart="2024-01-15T00:00:00Z" -Pduration=60

Notes for Next Migration

  • Spring Boot 4.0 has extensive package relocations - check imports carefully
  • Spring Batch 6.0 is a major breaking change - all item readers use builder pattern
  • Use Spring Cloud 2025.1.x with Spring Boot 4.0 (not 2026.x)
  • Test annotations moved to new packages - search/replace needed
  • Flyway requires starter dependency for proper auto-configuration
  • Modularized starters (batch-jdbc, webmvc-test) need explicit dependencies

Summary Statistics

Repo Java Migration SpringBoot Migration Total Time Major Issues
example-service 17 → 21 ✅ → 25 ✅ 3.4 → 3.5 ✅ → 4.0 ✅ ~6.5 hours 22+ (all resolved)

Common Issues Across Repos

Issue Repos Affected Standard Solution
Gradle version incompatibility example-service 8.10.2 for Java 21, 9.3.1 for Java 25
Lombok Java 25 incompatibility example-service Upgrade to 1.18.40+
Palantir Docker plugin deprecated example-service Replace with Google Jib
ByteBuddy/Mockito Java 25 example-service Add -Dnet.bytebuddy.experimental=true
JaCoCo Java 25 support example-service Upgrade to 0.8.14+
CI hardcoded Java version example-service Update workflow yaml files
Integration tests NO-SOURCE in Gradle 9 example-service Add explicit testClassesDirs and classpath
CI docker task not found example-service Update assemble.sh for Jib
PR title validation example-service Use conventional commits format
Spring Boot 4.0 @MockBean removed example-service Use @MockitoBean from new package
Spring Boot 4.0 package relocations example-service Update imports (see table above)
Spring Batch 6.0 builder-only pattern example-service Refactor to factory + builder pattern
Spring Cloud version confusion example-service Use 2025.1.x with Spring Boot 4.0
Flyway not auto-configured example-service Use spring-boot-starter-flyway

Lessons Learned

  1. Always check CI workflow files - Java versions are often hardcoded, not read from .java-version
  2. Gradle version matrix - Java 21 needs Gradle 8.x, Java 25 needs Gradle 9.x
  3. Plugin compatibility is the biggest challenge - Many Gradle plugins don't support Gradle 9
  4. Jib over Palantir - Jib is actively maintained and doesn't require Docker daemon
  5. Test frameworks need special attention - ByteBuddy/Mockito lag behind Java releases
  6. Stacked PRs require careful rebasing - Always keep the target version during conflict resolution
  7. Hidden bugs surface during migrations - Old plugins may mask schema/type errors
  8. Incremental upgrades are safer - 17 → 21 → 25 is better than direct 17 → 25
  9. Update ALL build scripts when changing plugins - scripts/assemble.sh must match build.gradle
  10. Gradle 9.x changes Test task behavior - Custom Test tasks need explicit source/classpath config
  11. PR titles matter - CI may validate conventional commits format
  12. Spring Boot 4.0 has extensive package relocations - Plan for import changes across codebase
  13. Spring Batch 6.0 is a breaking change - Setter-based configuration replaced with builders
  14. Spring Cloud 2025.1.x for Spring Boot 4.0 - Documentation may incorrectly suggest 2026.x
  15. Modularized starters require explicit dependencies - batch-jdbc, webmvc-test, flyway
  16. Data comparison requires clean consumer state - reset Kafka offsets before each branch export

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors