The Context
Recently, a client approached me with a specific requirement: update two of their Spring Boot applications to the latest available versions. With the recent availability of Spring Boot 4 support for Java 25, we decided it was the perfect opportunity to embrace the bleeding edge. While the benefits of migrating are numerous (you can ask your favorite AI for a list), our goal was to leverage three specific game-changing features: enhanced security, superior performance and the full power of Virtual Threads.
The Problem… and the “Other” Problem
We faced two distinct challenges. The first was migrating a proprietary API built with Spring Boot 3.4.x and Java 17. Although Spring Boot 4 introduces breaking changes, this felt like a manageable leap—mostly a matter of validating dependencies and auto-configurations.
The second application, however, was the real beast. It was a legacy system running on Spring Boot 2.4.x and Java 8, riddled with dependencies fetched from private repositories with questionable future compatibility. In this scenario, anything could happen. But as the saying goes, its bark was worse than its bite.
The Solution
After reviewing several migration guides and identifying the necessary changes, we decided to take a counter-intuitive approach. Instead of starting with the code, we started from the end: the infrastructure. We updated the Maven and Java versions in the Dockerfile first to prevent any CI/CD oversights. Since both applications live in AWS, Amazon Corretto was the natural choice:
# We changed the original version FROM maven:3.8.2-openjdk-8 AS build
# To the new Corretto image supporting Java 25
FROM maven:3.9.11-amazoncorretto-25 AS build
Pro-tip: Don’t forget to update your local Maven version to 3.9.x to support Java 25 and update your IDE references to avoid syntax highlighting errors.
Next, we tackled the pom.xml (Gradle users will follow a similar path), updating the Spring Boot parent version and properties:
<properties>
<java.version>25</java.version>
<maven-compiler-plugin.version>3.14.1</maven-compiler-plugin.version>
... more properties here ...
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.0</version>
<relativePath/>
</parent>
Here is the crucial step regarding the build process. We configured the Maven Compiler Plugin to support Java 25 using the --enable-preview argument. If you are migrating from older POM configurations, you might need to replace the old source and target tags with release:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<release>${java.version}</release>
<compilerArgs>
<arg>--enable-preview</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
Finally, going the extra mile to leave the code cleaner than we found it, we optimized the configuration by explicitly excluding unnecessary auto-configurations in the application.yml (or properties):
spring:
threads:
virtual-enabled: true # Explicitly enabling Virtual Threads for clarity
jmx:
enabled: false
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
- org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration
Summary of steps:
- Update local and CI/CD Maven to 3.9.x.
- Update
pom.xmlwith Java 25 and Spring Boot 4.0.0 versions. - Update and configure the Maven plugin to handle the
--enable-previewargument. - Disable unnecessary auto-configurations to streamline startup.
- Update the Dockerfile to use the correct base image (e.g., Corretto 25).
The Results
To be completely honest, the migration was smoother than anticipated. We completed the migration and AWS deployment for both applications within the time budget originally estimated for just one.
The most immediate impact was on startup times—a strong point of the new Spring Boot version. We went from an average of 6 seconds to under 3 seconds in our AWS environments. Furthermore, API response times dropped from over 1500ms to under 500ms (excluding the initial “warm-up” invocation, which naturally remains higher).
I wanted to share this brief guide because finding a consolidated, step-by-step resource for this specific stack was surprisingly difficult. If you are on the fence, I encourage you to make the jump. For well-structured projects following standard Software Engineering practices, the migration is practically transparent and offers a significant boost in performance and security.
Do you need help modernizing your legacy Java applications or optimizing your Spring Boot architecture? Let’s connect on LinkedIn or check out my services on Upwork.
Leave a comment