Migrating Spring Boot 3 to 4
Migrating Spring Boot 3.x to Spring Boot 4.1
Bash# 1. Baseline: ensure tests pass BEFORE touching anything mvn -q test # or ./gradlew test # 2. Bump Spring Boot version # pom.xml <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>4.1.0</version> </parent> # gradle (build.gradle) id 'org.springframework.boot' version '4.1.0' # 3. Rebuild and fix compilation errors (see Breaking Changes below) mvn -q compile # 4. Iterate on tests until green mvn -q test
Never attempt the version bump + all fixes in one shot. Follow the workflow strictly and commit after each stabilized step so regressions are isolated.
Progress:
- Step 0: Confirm baseline — all existing unit tests pass on Spring Boot 3.x
- Step 1: Bump Spring Boot parent/BOM version to 4.1.0 (and Spring Cloud BOM if used, see compatibility table)
- Step 2: Run
compile— collect ALL compilation errors before fixing (don't fix one and recompile repeatedly) - Step 3: Apply fixes grouped by category (see Breaking Changes Catalog)
- Step 4: Recompile until clean
- Step 5: Run test suite — collect all failures
- Step 6: Fix test failures iteratively, one category at a time, recompiling/retesting after each category
- Step 7: Run full build (
mvn verify/./gradlew build) including integration tests - Step 8: Manual sanity check of application startup (
mvn spring-boot:run) — check logs for deprecation warnings and config binding errors
Step 0 detail
If baseline tests are already failing, STOP and fix them first on Spring Boot 3.x. Never start a migration on red tests — you won't be able to distinguish pre-existing failures from migration-induced ones.
Step 2 detail — batch error collection
Run the compiler once, capture the full error list, then triage by root cause category (import path change, removed class, changed method signature, etc.) rather than fixing errors one-by-one in file order. Many errors share the same root cause.
Spring Boot 4.1 is built on Spring Framework 7 and requires Java 17 minimum (Java 21 recommended). It moves the baseline to Jakarta EE 11 / Servlet 6.1.
1. Java & baseline requirements
- Minimum Java version: 17. If project targets Java 11/8, upgrade JDK and
<java.version>/sourceCompatibilityfirst. - Jakarta EE baseline moves to 11:
jakarta.servletAPI → 6.1,jakarta.persistence→ 3.2.
2. Removed / relocated classes and packages
org.springframework.boot.web.servlet.error.ErrorController— check for deprecated methods removed in 4.x; use the currentErrorControllerinterface (singlegetErrorPath()method already removed since 2.x — verify no leftover legacy overrides).WebMvcConfigurerdeprecated methods removed — replace any calls to previously@Deprecatedmethods still present in 3.x with their designated replacements found in compiler errors.spring-boot-starter-webno longer transitively pulls Tomcat in the exact same module names in some layouts — checkmvn dependency:treeif you getClassNotFoundExceptionfor embedded server classes.@ConstructorBinding— fully removed (already deprecated since Boot 3.0). Remove the annotation entirely; constructor binding is now implicit for@ConfigurationPropertieswith a single non-default constructor.
3. Configuration properties changes
- Relaxed binding rule changes: kebab-case in YAML remains canonical; verify any properties using unusual casing that relied on legacy lenient binding.
spring.config.importsemantics: stricter validation of missing optional imports — prefix withoptional:explicitly, since silent ignore is removed.- Some previously deprecated properties (check
spring-boot-autoconfiguredeprecation warnings in 3.2+ logs) are removed in 4.x. Search yourapplication.yml/.propertiesfor any property that logged a deprecation warning in Spring Boot 3.2/3.3/3.4 — it is a removal candidate in 4.x. management.endpoints.web.exposure.includebehavior unchanged, but some actuator endpoint IDs renamed — check actuator response contracts in tests.
4. Spring Framework 7 changes (transitive impact)
RestTemplateremains but Spring emphasizesRestClient/WebClientas primary HTTP clients — no forced migration, but new deprecations may appear in logs.- Null-safety annotations (
@Nullable/@NonNull) migrated to JSpecify (org.jspecify.annotations) instead of Spring's ownorg.springframework.langpackage in framework source — if your code implements Spring interfaces and overrides methods with@Nullablefromorg.springframework.lang, check for signature mismatches; align to JSpecify if compiler complains about incompatible override annotations. - AOT / GraalVM native hints APIs may have package changes — if using
RuntimeHintsRegistrar, verify import paths still resolve.
5. Data / JPA
- Hibernate version bump (Hibernate 7.x) bundled — check for:
- Changed default behavior of
@GeneratedValuestrategies if usingAUTO. - Removed deprecated Hibernate-specific annotations replaced by JPA 3.2 equivalents.
- Changed default behavior of
spring-data-jparepository method return type strictness — some previously lenientOptional-vs-nullable return mismatches may now fail at startup validation.
6. Security
spring-securitymajor version bump aligned with Boot 4.1 — checkSecurityFilterChainbean configuration:- Deprecated
WebSecurityConfigurerAdapterfully removed (should already be gone since Boot 3.0 migration — if still present, this is the #1 compile blocker). - Lambda DSL is now the only supported configuration style for some legacy methods removed.
- Deprecated
7. Testing
@MockBean/@SpyBean(Spring Boot's own annotations) removed — replace with Mockito's@MockitoBean/@MockitoSpyBeanfromorg.springframework.test.context.bean.override.mockito.@WebMvcTest/@DataJpaTestslice test auto-configuration lists changed — if a slice test fails with missing bean, check whether an auto-configuration class was renamed/split.- JUnit 5 platform version alignment — ensure no direct pinned JUnit version conflicts with the new managed BOM version.
8. Dependency/BOM specific
- If using Spring Cloud, verify the compatible Spring Cloud release train for Boot 4.1 (do NOT guess — if the agent lacks internet access and the exact train name is unknown, flag this explicitly to the user rather than fabricating a version string).
- Third-party starters (e.g., springdoc-openapi, mapstruct-spring-extensions) pinned to Spring Boot 3.x APIs may need major version bumps — compilation errors referencing these libraries should be flagged to the user for manual dependency version lookup, since the agent has no internet access to verify latest compatible versions.
Example 1: @ConstructorBinding removal
Input:
Java@ConstructorBinding @ConfigurationProperties(prefix = "app") public class AppProperties { public AppProperties(String name) { ... } }
Output:
Java@ConfigurationProperties(prefix = "app") public class AppProperties { public AppProperties(String name) { ... } }
Example 2: @MockBean removal in tests
Input:
Java@SpringBootTest class OrderServiceTest { @MockBean private PaymentClient paymentClient; }
Output:
Java@SpringBootTest class OrderServiceTest { @MockitoBean private PaymentClient paymentClient; }
(import changes from org.springframework.boot.test.mock.mockito.MockBean to org.springframework.test.context.bean.override.mockito.MockitoBean)
Example 3: WebSecurityConfigurerAdapter still present
Input:
Java@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) { ... } }
Output:
Java@Configuration public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http... // lambda DSL configuration return http.build(); } }
- One version bump, isolated commit. Commit the raw dependency bump before any code fix, so
git diffclearly separates "version change" from "adaptation code". - Fix by category, not by file. Group compilation errors by root cause (e.g., all
@MockBeanoccurrences together) — much faster than file-by-file. - Re-run full test suite after each category fix, not just the affected test file — cross-cutting changes (e.g., security config) can break unrelated tests.
- Grep before you guess. Use
grep -rn "@ConstructorBinding\|WebSecurityConfigurerAdapter\|@MockBean\|@SpyBean"across the codebase at the start to size the migration effort before diving in. - Check startup logs, not just test results. Deprecation warnings at boot time often reveal issues that don't fail tests but will fail in production.
- Flag unknowns explicitly. When a third-party dependency's Spring Boot 4-compatible version is needed but unknown (no internet access), tell the user clearly rather than inventing a version number.
- Don't fix compilation errors one at a time with repeated recompiles — batch-collect and categorize first, it's far faster.
- Don't assume a property removal is a bug — check if it was deprecated in 3.2+ first; Spring Boot always deprecates for at least one minor version before removal.
- Don't silently swap
@MockBeanfor@Mock— semantics differ (@Mockdoesn't register in the Spring context); use@MockitoBean. - Don't invent Spring Cloud / third-party compatibility version numbers when offline — surface the uncertainty to the user instead of guessing.
- Don't skip the Step 0 baseline check — starting migration on a red test suite makes it impossible to know which failures are pre-existing.
- Don't bump Java version and Spring Boot version in the same commit — isolate them so build failures are attributable to one cause at a time.