How to upgrade old Java / Spring Boot apps. Many companies need this done.

A lot of production Java still runs on Java 8 or 11 with Spring Boot 2.x. Free open-source support for Spring Boot 2.7 ended in 2023, and every Spring Boot 3.x line has now left free OSS support too (3.5 ended in June 2026). Spring Boot 4.0 came out in November 2025 and 4.1 in June 2026. So if your app is still on 2.x, you are two major versions behind, and security scanners will keep flagging it.

The good news: the hardest jump is 2.x → 3.x, because of the javax → jakarta change. Once you're on a clean 3.5, moving to 4.x is a much smaller step. Below is the order I follow, and the problems that actually cost time.

The safe order of steps

  1. Get to the latest Spring Boot 2.7.x first. Fix every deprecation warning there. Most things removed in 3.0 were deprecated in 2.7, so this step makes the next one much easier.
  2. Upgrade the JDK to 17 or 21 while still on 2.7 (2.7 runs on Java 17). That keeps JDK problems separate from Spring problems.
  3. Jump to Spring Boot 3.5: javax → jakarta, Spring Security 6, Hibernate 6.
  4. Stabilise and release. Don't combine this with feature work.
  5. Then move to Spring Boot 4.x, following the official 4.0 migration guide.

Step 1: Let tools do the boring part

OpenRewrite has ready-made recipes that rewrite imports, properties and common APIs automatically. A dry run shows you what would change:

mvn -U org.openrewrite.maven:rewrite-maven-plugin:dryRun \
  -Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:RELEASE \
  -Drewrite.activeRecipes=org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0

Review the generated patch, then run rewrite:run. Recipes for later 3.x versions exist too. Expect it to do about 70–80% of the mechanical work, not all of it.

Also add the properties migrator temporarily. At startup it logs every renamed or removed property in your application.yml:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-properties-migrator</artifactId>
  <scope>runtime</scope>
</dependency>

Remove it once the warnings are gone.

Step 2: The javax → jakarta switch

Spring Boot 3 is built on Jakarta EE, so javax.persistence, javax.servlet, javax.validation and the others become jakarta.*. Your own code is easy (OpenRewrite handles it). The real trap is third-party libraries that still use javax.* internally. They compile fine and then fail at runtime with ClassNotFoundException or NoSuchMethodError.

Before you start, list every dependency and check that a Jakarta-compatible version exists:

mvn dependency:tree -Dincludes=javax.*

Anything that still pulls in javax.servlet or javax.persistence needs upgrading or replacing.

Step 3: Dependency conflicts that waste days

These are the problems I've seen most often:

  • SLF4J 1.7 vs 2.x. Spring Boot 3 uses SLF4J 2 and Logback 1.4+. If an old library drags in SLF4J 1.7 or an old Logback, you get "No SLF4J providers were found" and silent logging. Exclude the old versions, and let Spring Boot's dependency management pick them.
  • Jackson version mismatch. Errors like NoClassDefFoundError: com/fasterxml/jackson/databind/cfg/DatatypeFeature mean two Jackson versions are mixed. Never pin individual Jackson modules. Use the BOM version that Spring Boot manages.
  • "Unsupported class file major version 61/65." Something is still running on an older JDK: your IDE, Maven, the Jenkins agent or a Docker base image. 61 = Java 17 and 65 = Java 21. Check JAVA_HOME everywhere, not just on your laptop.

Rule of thumb: delete hard-coded versions from your pom.xml for anything Spring Boot already manages. Most conflicts disappear.

Step 4: Spring Security 6

WebSecurityConfigurerAdapter is gone. Configuration is now a SecurityFilterChain bean:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain api(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())          // only for stateless APIs
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .httpBasic(Customizer.withDefaults())
            .build();
    }
}

Watch for antMatchers → requestMatchers, and for authorizeRequests → authorizeHttpRequests. Also note that trailing-slash matching is off by default in Spring Framework 6, so /api/users/ no longer matches /api/users. That breaks clients silently, so test it.

Step 5: Other changes to check

  • Hibernate 6: some HQL and ID-generation behaviour changed. Run your repository tests against a real database (Testcontainers), not H2 only.
  • Auto-configuration registration: custom starters move from spring.factories to META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
  • Tracing: Spring Cloud Sleuth is replaced by Micrometer Tracing.

Step 6: From 3.5 to 4.x

Spring Boot 4 is built on Spring Framework 7 and still needs Java 17 as a minimum. Because 3.5 already removed most legacy code, this step is mostly dependency and configuration updates. Do it as its own release, run the properties migrator again, and read the official Spring Boot 4.0 migration guide for the changes that affect your stack, especially JSON (Jackson) handling and starter modules.

Release checklist

  • ☐ All tests green, and integration tests run against real DB/Kafka (Testcontainers)
  • ☐ No javax.* left in the dependency tree
  • ☐ No properties-migrator warnings at startup
  • ☐ The same JDK in IDE, CI, Docker image and production
  • ☐ Security rules tested, including trailing-slash URLs
  • ☐ Logs, metrics and traces visible in your monitoring
  • ☐ A rollback plan: the previous image is tagged and deployable
Need this done on your codebase?
I upgrade Java 8/11 and Spring Boot 2.x applications to Java 21 and Spring Boot 3.5 / 4.x, with a clear plan, small reviewable pull requests and no surprise downtime. I've spent 15 years on banking and payments backends (Maybank, UOB, TSYS).
See how I work → or email sheikhhameed007@gmail.com

Comments