Spring Boot 4.0 shipped in November 2025, and if you’re running Boot 3.x in production, you’re probably wondering how painful the upgrade is going to be.

The short answer: it’s not another javax-to-jakarta situation. There’s no namespace migration that touches every file in your project. But there are real breaking changes you need to know about, and a few of them will bite you if you just bump the version number and hope for the best.

This guide covers what actually changed, what breaks, and how to upgrade step by step. We’ve done this migration on several client applications now, and I’ll focus on the things that cost us time so they don’t cost you time.

What’s New in Spring Boot 4

Spring Boot 4 ships on top of Spring Framework 7 and Jakarta EE 11. Here’s what matters most.

Java 21+ Baseline

Boot 4 requires Java 21 as a minimum for the 4.0.x release line. If you’re still on Java 17, you need to upgrade your JDK first. Java 21 brings virtual threads, pattern matching, record patterns, and sequenced collections. If you’ve been putting off a JDK upgrade, this is the forcing function.

Modular Auto-Configuration

This is the biggest structural change in Boot 4. The monolithic spring-boot-autoconfigure jar has been split into smaller, focused modules. Instead of one massive jar containing auto-configuration for every technology Spring Boot supports, each technology now lives in its own module with its own starter.

What this means in practice: if your application uses JPA, web MVC, and Actuator, you now need explicit starter dependencies for each. The old setup where spring-boot-starter-web pulled in everything you might need is more granular now.

<!-- Boot 3.x — the web starter pulled in a lot -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- Boot 4.x — more explicit starters -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Spring Boot provides “classic” starter POMs as a bridge during migration. These include all modules but exclude transitive dependencies, giving you a setup similar to Boot 3. Use them to get running quickly, then migrate to the modular starters when you’re ready.

Spring Security 7

Boot 4 ships with Spring Security 7, which changes some defaults that will break your existing security configuration if you’re not expecting them:

  • CSRF protection is now enabled for API endpoints by default. Previously this only applied to form-based applications. If you have REST APIs that don’t send CSRF tokens, you’ll get 403 errors.
  • authorizeRequests() is removed. Use authorizeHttpRequests() exclusively.
  • OAuth2 client configuration properties were restructured. Check your spring.security.oauth2.client properties.
// Boot 3.x — this compiled but was deprecated
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()  // deprecated in 3.x, removed in 4.0
        .requestMatchers("/api/**").authenticated()
        .anyRequest().permitAll();
    return http.build();
}

// Boot 4.x — use authorizeHttpRequests()
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/**").authenticated()
            .anyRequest().permitAll()
        );
    return http.build();
}

Hibernate 7 and Spring Data 2025.1

Boot 4 ships with Hibernate 7.1 and Spring Data 2025.1. Hibernate 7 introduces the Semantic Query Model (SQM) for better query optimization, reduces SessionFactory bootstrap memory by about 12%, and is designed to work well with virtual threads by using ReentrantLock instead of synchronized blocks internally.

Spring Data 2025.1 now generates derived query methods as JPQL strings instead of using the Criteria API, which yields roughly 3.5x better query throughput on benchmarks. Repository query methods are also processed at build time by default, improving startup performance and GraalVM native image compatibility.

OpenTelemetry Starter

Spring Boot 4 introduces spring-boot-starter-opentelemetry, a first-party observability starter. With a single dependency, you get automatic instrumentation, OTLP export, and integration with backends like Grafana’s LGTM stack. If you’ve been wiring up Micrometer with Prometheus manually, this starter simplifies the setup considerably.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-opentelemetry</artifactId>
</dependency>
# application.yml
management:
  otlp:
    tracing:
      endpoint: http://localhost:4318/v1/traces
    metrics:
      endpoint: http://localhost:4318/v1/metrics

JSpecify Null-Safety Annotations

Spring Framework 7 moved from its own @Nullable / @NonNull annotations to standardized JSpecify annotations. JSpecify annotations use ElementType.TYPE_USE, which means they can annotate any usage of a type, including generics and array elements. Kotlin 2 translates these automatically into Kotlin nullability.

This mostly affects library authors and teams using strict null checking in their IDE. If you’re referencing Spring’s own @Nullable in your code, you’ll need to switch to org.jspecify.annotations.Nullable.

Declarative HTTP Clients

The HTTP service client interface, introduced experimentally in Boot 3.2, is now stable and fully supported. You can define HTTP clients as plain Java interfaces and let Spring generate the implementation.

@HttpExchange("/api/users")
public interface UserClient {

    @GetExchange
    List<User> findAll();

    @GetExchange("/{id}")
    User findById(@PathVariable Long id);

    @PostExchange
    User create(@RequestBody User user);
}

This removes the need for OpenFeign in many cases and integrates natively with Spring’s RestClient.

Breaking Changes You Need to Know

Not everything is a smooth upgrade. Here’s what actually breaks.

Removed Deprecated APIs

Spring Boot 4 removes roughly 36 deprecated classes from the 2.x and 3.x lines. If your Boot 3.x application compiles without deprecation warnings, you’re in good shape. If not, fix those warnings before attempting the upgrade.

Key removals to watch for:

  • HttpMessageConverters is replaced by ClientHttpMessageConvertersCustomizer and ServerHttpMessageConvertersCustomizer
  • Elasticsearch RestClient is replaced by Rest5Client
  • PropertyMapper.alwaysApplyingWhenNonNull() is removed, use always() instead
  • spring.freemarker.enabled and spring.groovy.template.enabled** are removed

Undertow Removed

Undertow support is gone because it doesn’t support Servlet 6.1 yet. If your applications use Undertow as the embedded server, switch to Tomcat or Jetty.

# Boot 3.x with Undertow — this no longer works
server:
  undertow:
    threads:
      worker: 200

# Boot 4.x — switch to Tomcat or Jetty
server:
  tomcat:
    threads:
      max: 200

Jakarta EE 11 Alignment

Boot 4 moves to Jakarta EE 11, which means Servlet 6.1, JPA 3.2, and Bean Validation 3.1 baselines. Your application code should already be using jakarta.* packages from the Boot 3.x migration, but your third-party libraries need to be compatible too. Check that every library you depend on supports Jakarta EE 11.

Configuration Property Renames

Several configuration properties were renamed or removed. Spring Boot provides the spring-boot-properties-migrator module that analyzes your environment at startup and prints diagnostics.

<!-- Add temporarily during migration -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-properties-migrator</artifactId>
    <scope>runtime</scope>
</dependency>

Notable property changes:

# Boot 3.x
spring:
  jackson:
    generator:
      auto-close-target: true
    parser:
      allow-comments: true

# Boot 4.x
spring:
  jackson:
    json:
      write:
        auto-close-target: true
      read:
        allow-comments: true

DevTools Live Reload Disabled by Default

Live reload is now off by default. If your development workflow depends on it, explicitly enable it:

spring:
  devtools:
    livereload:
      enabled: true

Spring Batch In-Memory by Default

Spring Batch no longer requires a database for job metadata. It runs in-memory by default now. If you need persistent job state (and for production workloads, you almost certainly do), configure a datasource explicitly.

Step-by-Step Migration Process

Here’s the process we follow when migrating client applications from Boot 3 to Boot 4.

Step 1: Upgrade to Spring Boot 3.5.x First

This is the most important step, and the one most people skip. Spring Boot 3.5 exists specifically as a bridge to 4.0. It deprecates everything that’s removed in 4.0 and gives you clear compiler warnings about what needs to change.

<!-- pom.xml — start here -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.5.1</version>
</parent>

Build your project. Fix every deprecation warning. Run your test suite. Don’t move to Boot 4 until 3.5.x builds clean.

Step 2: Upgrade Java to 21+

Boot 4.0.x requires Java 21. Update your pom.xml or build.gradle:

<properties>
    <java.version>21</java.version>
</properties>

Update your CI pipeline, Docker images, and deployment targets to use JDK 21. This is also a good time to enable virtual threads if you haven’t already.

Step 3: Bump to Spring Boot 4.0.x

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.0.2</version>
</parent>

Add the properties migrator to catch configuration issues:

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

Step 4: Fix Compilation Errors

Work through the compilation errors systematically. The most common ones:

  1. Security DSL changes — replace authorizeRequests() with authorizeHttpRequests()
  2. Removed deprecated classes — follow the Javadoc replacement hints from Boot 3.5
  3. Modular starter imports — add missing starters for auto-configuration classes that are no longer bundled together
  4. Third-party library compatibility — upgrade libraries that don’t support Jakarta EE 11 yet

Step 5: Update Configuration Properties

Review the startup diagnostics from the properties migrator. Rename any deprecated properties. Common renames include Jackson, server, and template engine properties.

Step 6: Test Everything

Run your full test suite. Pay extra attention to:

  • Security tests — CSRF and authorization behavior changed
  • Database tests — Hibernate 7 may generate slightly different SQL
  • Integration tests — Spring Framework 7’s test context pausing can affect test execution order assumptions
  • Batch jobs — verify job metadata persistence if you depend on it

Step 7: Remove the Properties Migrator

Once everything works, remove spring-boot-properties-migrator. It’s a diagnostic tool, not a permanent dependency.

Automate With OpenRewrite

If you’ve read our guide on automating Spring Boot migrations with OpenRewrite, you know this is the fastest way to handle the mechanical parts of a migration. OpenRewrite has a dedicated Boot 4 recipe set that handles property renames, deprecated API replacements, and modular starter migration.

Maven

Run it without modifying your pom.xml:

mvn -U org.openrewrite.maven:rewrite-maven-plugin:run \
  -Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:LATEST \
  -Drewrite.activeRecipes=org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0

Gradle

Add the plugin and recipe dependency:

plugins {
    id 'org.openrewrite.rewrite' version '7.5.0'
}

dependencies {
    rewrite 'org.openrewrite.recipe:rewrite-spring:latest.release'
}

rewrite {
    activeRecipe 'org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0'
}

Then run:

gradle rewriteRun

What OpenRewrite Handles

The Boot 4 recipe set covers:

  • Property renames in application.properties and application.yml
  • Deprecated API replacements — swaps old method calls for their replacements
  • Modular starter migration — analyzes your imports and adds the appropriate granular starters
  • Security DSL migration — updates authorizeRequests() to authorizeHttpRequests()
  • Java version updates in build files

OpenRewrite handles the mechanical work. You still need to review the changes, handle custom code that the recipes can’t detect, and run your tests.

Performance Improvements to Expect

Boot 4 isn’t just about API changes. There are real performance wins.

Faster Startup

The modular auto-configuration means Spring Boot only loads the modules your application actually uses. Combined with AOT processing improvements and GraalVM 24 alignment, you’ll see faster startup times. Native image builds benefit from the unified reachability metadata format in GraalVM 25, producing leaner executables.

Teams we’ve worked with have seen startup times drop by 15-25% on the JVM and even more with native images.

Lower Memory Usage

Hibernate 7’s optimized entity metadata loading reduces bootstrap memory by about 12%. Spring Data’s build-time repository processing eliminates reflection-based query generation at runtime. Combined with the smaller modular jars, you’re loading less code into memory.

Better Throughput

Spring Data’s JPQL-based derived queries show roughly 3.5x improvement in query throughput on benchmarks. Hibernate 7 is Loom-friendly by design, so virtual threads work without the pinning issues that plagued earlier versions. If you’re running Java 21+ with virtual threads enabled, database-heavy applications should see meaningfully better throughput under load.

Improved Observability

The new OpenTelemetry starter gives you traces, metrics, and logs through a single dependency with OTLP export. If you’re currently running a custom Actuator and Prometheus setup, the OpenTelemetry starter is worth evaluating. It integrates with the same backends but with less configuration overhead.

Common Pitfalls

A few things that caught us during real migrations.

Third-Party Library Compatibility

The Jakarta EE 11 baseline trips up libraries that haven’t released compatible versions yet. Before you start the migration, check every dependency against Jakarta EE 11 compatibility. The ones that cause the most issues are older JDBC drivers, caching libraries, and anything that bundles its own servlet code.

Security Test Failures

CSRF being enabled by default for API endpoints is the single most common source of test failures after the upgrade. If your tests POST to REST endpoints without CSRF tokens, they’ll fail with 403. Either add CSRF tokens to your tests or explicitly disable CSRF for your API endpoints:

@Bean
SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
    http
        .securityMatcher("/api/**")
        .csrf(csrf -> csrf.disable())
        .authorizeHttpRequests(auth -> auth
            .anyRequest().authenticated()
        );
    return http.build();
}

Modular Starter Discovery

After switching to modular starters, you might find that auto-configuration classes you depended on are no longer on the classpath. The symptom is usually a bean not being created that used to exist. Check your application’s startup logs for missing auto-configuration classes and add the corresponding starter.

Hibernate SQL Changes

Hibernate 7’s SQM can generate different SQL than Hibernate 6. This is usually fine, but if you have tests that assert on exact SQL strings or if you’ve tuned database indexes around specific query shapes, verify that the generated queries still match your expectations.

Should You Upgrade Now?

If you’re starting a new project, use Boot 4. No question.

For existing applications, it depends. If you’re on Boot 3.5.x with clean deprecation warnings, the migration is manageable and the performance benefits are real. If you’re still on Boot 3.0 or 3.1, upgrade to 3.5.x first and stabilize before jumping to 4.

The one thing I’d avoid is waiting too long. Boot 3.x will eventually fall out of the support window, and migrating from a stale 3.x version to 4.x is harder than incremental upgrades.

If your team is stretched thin or the codebase is large, this is exactly the kind of migration work we do at Katyella. We’ve handled Boot 2-to-3 and now 3-to-4 migrations for enterprise applications. Reach out if you want to talk through your migration plan or need hands-on help.

Java Modernization Readiness Assessment

15 questions your team should answer before starting a migration. Takes 10 minutes. Could save you months.