// OSSeva Blog
MigrationSpring Boot 3 Migration Guide: From Spring Boot 2.7 to 3.x, and On to 4.0
The short answer
A Spring Boot 2 to 3 migration has four parts that cannot be skipped. You move to Java 17, rename the Java EE packages from javax to jakarta, rewrite security configuration for Spring Security 6, and retest persistence on Hibernate 6. Everything else is a list of renamed properties and changed defaults. Spring's own advice is to start from the latest 2.7.x release and to move to Spring Security 5.8 before you touch Spring Boot 3. Once on 3.x, the jump from 3.5 to 4.0 is smaller but has its own breaking changes: Jakarta EE 11, Spring Framework 7, Jackson 3 and modular starters.
The dates that set the deadline
These are the support dates spring.io publishes for each Spring Boot line. Open source support means free releases with security fixes. Commercial support is Broadcom's paid Tanzu Spring offering.
| Spring Boot line | Open source support ended or ends | Commercial support ends |
|---|---|---|
| 2.7.x | 30 June 2023 (last open source release 2.7.18) | 30 June 2029 |
| 3.5.x | 30 June 2026 | 30 June 2032 |
| 4.0.x | 31 December 2026 | 31 December 2027 |
| 4.1.x | 31 July 2027 | 31 July 2028 |
The practical reading: any application still on 2.7 has gone more than three years without free security fixes, and an application that finished its Spring Boot 3 migration on 3.5 is already past open source support again. Plan the 3.x work with 4.x in view.
Before you change a version number
- Upgrade to the latest 2.7.x. The Spring Boot 3.0 migration guide asks for this first, so that you build against the newest dependencies of the 2.x line. Spring Boot 2.7.18 requires Java 8 and is compatible up to and including Java 21.
- Clear deprecation warnings. Classes, methods and properties deprecated in Spring Boot 2.x were removed in 3.0. A clean 2.7 build with no deprecation warnings is the best predictor of a short migration.
- Move to Spring Security 5.8. The Spring Security team released 5.8 to make the jump to 6.0 smaller. Most of the 6.0 changes can be adopted on 5.8 and tested while the rest of the stack is still on Boot 2.
- List the dependencies Spring Boot does not manage. Spring Cloud is the usual one. Each release train supports specific Boot lines: 2022.0 (Kilburn) for Boot 3.0 and 3.1, 2025.0 (Northfields) for 3.5, and 2025.1 (Oakwood) for 4.0. Find the matching version of every unmanaged library before the upgrade, not during it.
Step 1: Java 17 as the baseline
Spring Boot 3.0 requires Java 17 or later and no longer supports Java 8. Spring Boot 4.0 keeps the same minimum and encourages the latest LTS release. Because 2.7.18 already runs on Java 17 and 21, the lowest-risk order is to move the JVM first, on Spring Boot 2.7, and ship that to production before changing the framework. JVM problems and framework problems then show up in separate releases.
The failures on Java 17 come from the build more than from your code. JDK 17 strongly encapsulates JDK internals, so libraries that reached into them through reflection fail at runtime unless upgraded. Annotation processors and bytecode tools (Lombok, Mockito and its ByteBuddy dependency, code coverage agents, older Maven and Gradle plugins) need versions that support Java 17. Upgrade those first and the rest of the codebase usually compiles.
Step 2: javax to jakarta
Spring Boot 3.0 moved every Jakarta EE dependency to the version in Jakarta EE 10, for example Servlet 6.0 and JPA 3.1. The packages move with it: javax.servlet becomes jakarta.servlet, javax.persistence becomes jakarta.persistence, and the same for validation, annotations, transactions, JAX-RS, JAXB, mail and JMS. Packages that belong to Java SE, such as javax.sql and javax.crypto, do not move, which is why a blind find-and-replace breaks the build.
The rename itself is mechanical. The work is the dependency tree. The migration guide warns that older Java EE artifacts must not remain on the classpath directly or transitively, so jakarta.servlet:jakarta.servlet-api replaces javax.servlet:javax.servlet-api everywhere, and every library that touches a servlet, an entity or a validator needs a Jakarta build. A WAR deployed to an external Tomcat 9 needs Tomcat 10.1 or a Jakarta EE 10 server. Our javax to jakarta migration guide covers the package list, the tools and the dependency audit in detail.
Step 3: Spring Security 6 configuration
Security configuration is the part most likely to compile and still behave differently. Spring Security 6.0 made these changes, all of which can be adopted on 5.8 first:
WebSecurityConfigurerAdapteris removed. Publish aSecurityFilterChainbean for HTTP rules, aWebSecurityCustomizerbean for ignored paths, and anAuthenticationManagerbean where you configured one through the adapter.antMatchers,mvcMatchersandregexMatchersare removed. UserequestMatchers, which picks an MVC matcher when Spring MVC is on the classpath and an Ant matcher otherwise.authorizeRequestsgives way toauthorizeHttpRequests, which usesAuthorizationManagerinstead of access decision voters. CustomAccessDecisionVoterandAccessDecisionManagerclasses need rewriting.@EnableGlobalMethodSecuritybecomes@EnableMethodSecurity.@Configurationis no longer part of the@Enable*annotations, so add it to each class that uses them.- Authorization applies to every dispatch type. Error pages and forwards are now checked too, which can turn a working error page into a 403. Spring Boot 3 sets the filter for every dispatch type, configurable with
spring.security.filter.dispatcher-types. - The security context must be saved explicitly.
SecurityContextHolderFilteronly reads the context; custom authentication code that relied on automatic saving must call theSecurityContextRepositoryitself. - CSRF tokens are loaded lazily and protected against BREACH by default. Single-page applications that read the token from a cookie often need
CsrfTokenRequestAttributeHandlerconfigured to keep working.
A typical Boot 2 configuration and its Boot 3 form:
// Spring Boot 2.x
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/actuator/health").permitAll()
.anyRequest().authenticated()
.and().httpBasic();
}
}
// Spring Boot 3.x
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health").permitAll()
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
Step 4: Hibernate 6
Spring Boot 3.0 uses Hibernate 6.1, under the new org.hibernate.orm group ID. Hibernate 6 rewrote the query engine and type system, so the persistence layer needs its own test pass even when it compiles. The changes that break running applications most often:
- Sequence naming. Hibernate 6 creates a sequence per entity hierarchy, named
<entity>_seq, instead of a singlehibernate_sequence. Entities using@GeneratedValuewith the default AUTO strategy expect those sequences to exist. Settinghibernate.id.db_structure_naming_strategy=legacykeeps the old behaviour while you migrate. - Allocation size. Implicit sequences now follow the JPA default allocation size of 50. Seed data loaded with
import.sqlhas to reset each sequence to a value that clears the existing IDs. - The old ID switch is gone.
spring.jpa.hibernate.use-new-id-generator-mappingswas removed because Hibernate no longer supports the old generator mappings. - Type mapping.
UUIDmaps to a nativeuuidcolumn where the database has one, which can fail schema validation on existing binary columns (hibernate.type.preferred_uuid_jdbc_type=BINARYrestores the old mapping). Native queries returningBIGINT, includingcount(), now returnLonginstead ofBigInteger. - Queries. The legacy Hibernate Criteria API is removed, so those queries move to JPA Criteria.
DISTINCTis no longer needed to remove duplicate parents from a fetch join and is always passed through to SQL.
Step 5: Observability and Micrometer
Spring Boot 3.0 builds on Micrometer 1.10 and its Observation API, and this changes how metrics are produced. The Boot 2 instrumentation filters were removed: WebMvcMetricsFilter is replaced by Spring Framework's ServerHttpObservationFilter, and MetricsRestTemplateCustomizer by ObservationRestTemplateCustomizer. Custom *TagsProvider and *TagsContributor beans are deprecated and no longer used by default; their logic moves into observation conventions. Check dashboards and alerts after the upgrade, because metric tags can change.
Tracing moves too. Spring Cloud Sleuth's last minor version is 3.1, and its core moved into Micrometer Tracing, so Boot 3 applications use Micrometer Tracing with a Brave or OpenTelemetry bridge. Smaller actuator changes catch teams out as well:
- Metrics export properties moved from
management.metrics.export.<product>tomanagement.<product>.metrics.export, for examplemanagement.prometheus.metrics.export. - The
httptraceendpoint is renamedhttpexchanges, andHttpTraceRepositorybecomesHttpExchangeRepository. - The
/envand/configpropsendpoints mask every value by default;management.endpoint.env.show-valuescontrols it. - Only the health endpoint is exposed over JMX by default.
Step 6: Let the properties migrator find renamed properties
Several configuration properties were renamed or removed in 3.0. Spring Boot ships a module that reports them at startup and temporarily maps the old names to the new ones while the application runs:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
With Gradle, use runtimeOnly("org.springframework.boot:spring-boot-properties-migrator"). Common findings include spring.redis.* moving to spring.data.redis.*, Cassandra moving from spring.data.cassandra.* to spring.cassandra.*, server.max-http-header-size becoming server.max-http-request-header-size, and SAML2 identity-provider properties becoming asserting-party. Fix each one in your configuration, then remove the module. It is a migration aid, not something to ship.
Other changes that only show up at runtime
- Trailing slashes. Spring Framework 6 no longer matches
/greeting/to a/greetingmapping by default, so those requests return 404. - Auto-configuration registration. Registering auto-configurations in
spring.factoriesunderEnableAutoConfigurationis no longer supported. Internal starters must list them inMETA-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. - HTTP clients. Spring Framework 6 dropped Apache HttpClient 4 support for
RestTemplatein favour ofhttpclient5. Without it,RestTemplatecan quietly fall back to the JDK client. - Logging. The default log date format is now ISO-8601 with a
Tseparator and a time zone offset, which can break log parsers. - Coordinates and removals. The MySQL driver is now
com.mysql:mysql-connector-j, the Elasticsearch high-level REST client is gone, and support for Apache ActiveMQ, Atomikos, EhCache 2 and Hazelcast 3 was removed. - Spring Batch 5.
@EnableBatchProcessingnow tells Boot's auto-configuration to back off, and running several jobs at startup needsspring.batch.job.name.
Step 7: Automate with OpenRewrite
OpenRewrite recipes do much of the mechanical work. org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0 chains the Spring Boot 2.7 upgrade, the Java 17 upgrade, the Spring Framework 6.0 and Spring Security 6.0 migrations, the Hibernate 6.1 migration, the Spring Batch 4 to 5 migration, the Spring Cloud 2022 upgrade and the Spring Boot 3.0 property renames. Later recipes build on it:
| Recipe | What it does |
|---|---|
org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_0 | 2.7 to 3.0, including Java 17, jakarta, Security 6 and Hibernate 6.1 |
org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_5 | Up to 3.5, chaining the 3.x recipes before it |
org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0 | 3.5 to 4.0, chaining the 3.5 recipe first |
org.openrewrite.java.spring.security6.UpgradeSpringSecurity_6_0 | The security changes on their own |
org.openrewrite.java.migrate.UpgradeToJava17 | The Java 17 move on its own, for the first step |
Run a recipe on a branch and review the diff like any other pull request. It will not decide how custom access decision voters should become authorization managers, or whether your sequences match Hibernate 6. Check the licence before adopting it in a commercial build: the OpenRewrite documentation lists these Spring recipes under the Moderne Source Available License, with precompiled artifacts for Moderne customers and source builds for non-commercial use. The Spring Boot Migrator project and IntelliJ IDEA's migration support are the other tools the Spring guide names.
Testing and validation
- Compile every module against Spring Boot 3 and fix compilation errors before running anything.
- Run unit tests and fix behaviour changes, not just renamed APIs.
- Run integration tests against a real application context with the properties migrator in place. Many auto-configuration changes only surface at runtime.
- Test authentication flows, error pages, transactions and ID generation end to end in a staging environment. These are where Security 6 and Hibernate 6 differ without failing a build.
- Compare metrics and traces with production before cutting over, so dashboards and alerts survive the Micrometer change.
From 3.0 to 3.5
Once an application runs on 3.0, each later minor release is a smaller step, but each one can remove APIs deprecated in earlier 3.x releases. Moving one minor at a time and reading each release's notes keeps those removals visible. The UpgradeSpringBoot_3_5 recipe applies the intermediate recipes in order if you want to go straight there. 3.5 is the last 3.x line, and its open source support ended on 30 June 2026.
Spring Boot 3.5 to 4.0
The Spring Boot 4.0 migration guide follows the same pattern: upgrade to the latest 3.5.x first, clear deprecations because they are removed in 4.0, then move. The main changes:
- Java 17 is still the minimum. Kotlin applications need Kotlin 2.2 or later, and GraalVM native images need GraalVM 25 or later.
- Jakarta EE 11 and Servlet 6.1. Spring Boot 4 requires Spring Framework 7. Undertow does not support Servlet 6.1 yet, so the Undertow starter is gone.
- Modular starters. Spring Boot is now split into smaller modules, and technologies that previously worked with only a third-party jar now need a starter, for example
spring-boot-starter-flywayorspring-boot-starter-liquibase.spring-boot-starter-classicbrings everything back while you sort out the dependencies. - Jackson 3. Jackson 3 uses new group IDs and the
tools.jacksonpackage, except for annotations.Jackson2ObjectMapperBuilderCustomizerbecomesJsonMapperBuilderCustomizer, and a deprecatedspring-boot-jackson2module exists as a stop-gap. - The properties migrator applies again. Add it for the 4.0 upgrade and remove it afterwards, as in step 6.
Open source support for 4.0 ends on 31 December 2026, so a team starting the move now should target 4.1, supported to 31 July 2027.
When the migration outlasts the support window
Large estates rarely move every service before the date. OSSeva's Spring continuation support patches Spring Boot 2.7 and the 3.x lines including 3.5, Spring Framework 5.3 and Spring Security 5.8, so the services still waiting for their migration slot get security fixes while the work proceeds. The Spring Boot 2, Spring Boot 3.5 and Spring Boot 4.0 end-of-life pages set out the dates, and the Spring Boot extended support page lists coverage by version.
Frequently asked questions
Does Spring Boot 3 require Java 17?
Yes. Spring Boot 3.0 requires Java 17 or later and dropped Java 8. Spring Boot 4.0 has the same minimum.
Can I go straight from Spring Boot 2.7 to 3.5?
Yes. OpenRewrite's UpgradeSpringBoot_3_5 recipe chains the 3.0 to 3.4 steps. Do the Java 17 and Spring Security 5.8 work on 2.7 first either way, so the framework upgrade carries less at once.
Should I upgrade to Spring Boot 3.5 or 4?
For a 2.7 application, 3.5 first. The Spring Boot 4.0 guide expects you to start from the latest 3.5.x with deprecations cleared. Plan for 4.1 straight after, since 3.5 is already past open source support.
What does spring-boot-properties-migrator do?
It reports configuration properties that were renamed or removed, and maps the old names to the new ones at runtime while you migrate. Remove it once your configuration uses the new names.
Does OpenRewrite do the whole migration?
No. It handles renames, dependency upgrades and common configuration patterns. Custom security voters, Hibernate sequence and type changes, observability conventions and anything that depends on runtime behaviour still need engineers and tests.
Is Spring Boot 2.7 still supported?
Not by the open source project. Free releases ended with 2.7.18, after open source support ended on 30 June 2023. Broadcom's commercial support runs to 30 June 2029, and third-party extended support such as OSSeva's also covers it.
Tags
Ready to get your open source under control?
Talk to an OSSeva engineer about CVE coverage, compliance, and migration support for your stack.