Upgrading to Spring Boot 4 is about more than changing a version number in your pom.xml.
Before you upgrade, it's worth taking stock of your project: your Java version, Spring Cloud release train, JSON serialization, servlet container, Vaadin version, Spring AI dependencies, native-image builds, CI pipeline, and deployment setup all play a role.
This guide focuses on the migration process rather than new features. We'll cover how to prepare your project, automate as much of the upgrade as possible, and test the areas most likely to be affected.
TL;DR
Start from a clean Spring Boot 3.5 branch, upgrade to the latest 3.5 patch release approved for your project, remove deprecated APIs and properties, review unmanaged dependencies, align Spring Cloud, and run the Spring Boot 4 OpenRewrite recipe. Then move to your target Spring Boot 4 release and test the parts that usually break: JSON, servlet container, security, Spring AI, Vaadin, native images, and packaged startup.
As of June 30, 2026, the examples below target Spring Boot 4.1.0, Spring AI 2.0.0, and Vaadin 25.2.1 for Vaadin projects. If your organization is standardizing on Spring Boot 4.0.x or a specific Vaadin patch release, keep the same process and substitute the approved versions.

Start with the project you actually have
Run these before changing versions:
./mvnw -v ./mvnw help:evaluate -Dexpression=project.parent.version -q -DforceStdout ./mvnw dependency:tree -Dincludes=org.springframework.boot,org.springframework,org.springframework.cloud,com.vaadin,org.springframework.ai Write down the Spring Boot version, Java version, Spring Cloud release train, Vaadin version, Spring AI version, embedded server, native-image path, custom Jackson usage, and explicit dependency versions.
That list is your migration map. Without it, you are just changing versions and waiting for the build to tell you what it found first.
Move to the latest Spring Boot 3.5 first
Do not jump straight from an old 3.5 patch to Spring Boot 4. Upgrade to the latest 3.5 patch release your team has approved, then make the app clean there.
For a Maven project using the Spring Boot parent, update the parent version first:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.x</version>
<relativePath/>
</parent>
Then run:
./mvnw clean verify
Fix warnings and deprecated usage while the app is still on 3.5:
./mvnw clean test -Dmaven.compiler.showWarnings=true
grep -R "spring-boot-properties-migrator\|@MockBean\|@SpyBean\|com.fasterxml.jackson\|elemental.json" \
pom.xml src/main src/test 2>/dev/null
Spring Boot 4 removes APIs and properties deprecated during the 3.x line. Fixing those before the major upgrade keeps the Boot 4 diff smaller and easier to review.
Check dependency overrides
Spring Boot can only manage dependencies you let it manage. Print the dependency tree for the parts that usually matter:
./mvnw dependency:tree \
-Dincludes=org.springframework,org.springframework.boot,org.springframework.cloud,com.vaadin,org.springframework.ai,com.fasterxml.jackson,tools.jackson,jakarta.servlet,org.flywaydb,org.liquibase,io.micrometer
If Spring Boot already manages a dependency, remove the explicit version unless you have a reason to keep it. If you keep it, leave a short comment in pom.xml explaining why.
If the service uses Spring Cloud, align the release train with Boot 4. The Spring Boot 4 OpenRewrite recipe currently includes migration to Spring Cloud 2025.1:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2025.1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Run OpenRewrite on a branch
OpenRewrite has a Spring Boot 4 recipe: org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0. The recipe migrates the project to the Boot 4.0 line and chains related framework migrations such as Spring Framework 7, Spring Security 7, Spring Cloud 2025.1, modular starters, configuration property changes, and common test annotation replacements.
Use it for the mechanical edits, then review the diff like production code.
git checkout -b boot-4-migration
./mvnw -U org.openrewrite.maven:rewrite-maven-plugin:run \
-Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-spring:RELEASE \
-Drewrite.activeRecipes=org.openrewrite.java.spring.boot4.UpgradeSpringBoot_4_0 \
-Drewrite.exportDatatables=true
git status --short
git diff -- pom.xml
git diff -- src/main src/test
OpenRewrite can handle a lot of repetitive work. It cannot know your API contracts, deployment assumptions, security rules, or whether your JSON output must stay byte-for-byte compatible.
Upgrade to your target Spring Boot 4 release
For a project targeting Spring Boot 4.1.0:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.0</version>
<relativePath/>
</parent>
Then build:
./mvnw clean verify
Sort failures by category: dependency alignment, modular starters, JSON and Jackson 3, security, servlet container, Spring Cloud, Spring AI, Vaadin, tests, and native-image support.
Spring Boot 4 is more modular. Prefer explicit starters where Boot now expects them:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-flyway</artifactId>
</dependency>
Watch the common tripwires
Undertow support was dropped because Spring Boot 4 requires Servlet 6.1:
grep -R "spring-boot-starter-undertow\|undertow" \
pom.xml src/main/resources 2>/dev/null
If you use Undertow, switch to Tomcat or Jetty and test thread pools, request limits, access logs, compression, and error responses.
Spring Boot 4 prefers Jackson 3. Search for custom JSON behavior:
grep -R "ObjectMapper\|JsonSerializer\|JsonDeserializer\|JsonMixin\|PropertyNamingStrategies\|com.fasterxml.jackson\|tools.jackson" \
src/main src/test 2>/dev/null
Then test actual payloads:
./mvnw spring-boot:run
curl -i http://localhost:8080/actuator/health
curl -i -H "Accept: application/json"
A green startup log is not a JSON compatibility test.
Make Spring AI a decision point
If your app uses Spring AI, do not leave it as an afterthought. The current Spring AI docs state that Spring AI 2.0.x supports Spring Boot 4.0.x and 4.1.x. That gives teams a clear decision:
- Stay on Spring Boot 3.5 with the Spring AI 1.1.x line until the service is ready for a larger AI dependency update.
- Move to Spring Boot 4 with Spring AI 2.0.x and test the AI integration as part of the migration.
Find Spring AI dependencies and properties:
grep -R "spring-ai\|spring.ai" pom.xml src/main/resources src/main/java src/test 2>/dev/null ./mvnw dependency:tree -Dincludes=org.springframework.ai
If you move to Spring AI 2.0.x, update the BOM deliberately:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>2.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Starter names changed. For OpenAI:
<!-- Before -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
</dependency>
<!-- After -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
Vector store starters follow the same pattern, for example spring-ai-starter-vector-store-redis. Also check model-selection properties:
spring.ai.model.chat=openai spring.ai.openai.api-key=${OPENAI_API_KEY} Test model client auto-configuration, API key loading, vector store schema behavior, tool calling, streaming responses, and retry or timeout settings. If those tests are thin, write them before the migration, not after.
Vaadin 25.2.1, Spring Boot 4.1.0, and Java 21
For a Vaadin 24 app moving to Vaadin 25.2.1, Spring Boot 4.1.0, and Java 21, set the versions explicitly:
<properties>
<java.version>21</java.version>
<vaadin.version>25.2.1</vaadin.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.1.0</version>
<relativePath/>
</parent>
Check Vaadin dependency management:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Then run:
./mvnw clean verify
Vaadin 25 requires Java 21 or later, Spring Boot 4 / Spring Framework 7 for Spring-based apps, and Servlet 6.1. It also uses Jackson 3 and replaces Elemental JSON in affected low-level APIs.
Search for old imports:
grep -R "elemental.json\|com.fasterxml.jackson" src/main/java src/test/java 2>/dev/null
Code using low-level Vaadin JSON types may need to move to Jackson 3 packages such as tools.jackson.databind.*:
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
private final ObjectMapper objectMapper = new ObjectMapper();
JsonNode json = objectMapper.readTree(payload);
String value = json.path("value").asString();
If the app uses LumoUtility, load the utility stylesheet explicitly. In Vaadin 25, theme.json lumoImports no longer loads Lumo Utility Classes:
import com.vaadin.flow.component.dependency.StyleSheet;
import com.vaadin.flow.component.page.AppShellConfigurator;
import com.vaadin.flow.theme.lumo.Lumo;
@StyleSheet(Lumo.UTILITY_STYLESHEET)
public class AppShell implements AppShellConfigurator {
}
Search for both:
grep -R "LumoUtility\|lumoImports" src/main frontend 2>/dev/null
If your app uses package scanning configuration, rename:
vaadin.whitelisted-packages=com.example.application to:
vaadin.allowed-packages=com.example.application Search all config files:
grep -R "vaadin.whitelisted-packages\|vaadin.allowed-packages" \ src/main/resources pom.xml 2>/dev/null
Vaadin 25 also requires Node.js 24 or later for frontend builds. If your CI scripts refer to Vaadin's old single Node path, update them to account for version-specific Vaadin-managed Node directories.
After the rename and version changes, run the app and open a real view:
./mvnw spring-boot:run
Check that views load, styles are present, routes work, login still works, and production frontend builds still pass. A backend-only smoke test can miss broken UI styles.
Use the properties migrator, then remove it
Add spring-boot-properties-migrator during the migration:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-properties-migrator</artifactId>
<scope>runtime</scope>
</dependency>
Run the app, read the startup output, update config, then remove the migrator:
./mvnw spring-boot:run grep -R "spring-boot-properties-migrator" pom.xml src/main/resources 2>/dev/null
If the second command still prints a match after cleanup, the migration is not finished.
Test the migration where it can break
Run the normal build and a packaged-app smoke test:
./mvnw clean verify
./mvnw spring-boot:build-image
docker run --rm -p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=local \
your-image-name:latest
curl -fsS http://localhost:8080/actuator/health
curl -fsS
If the service ships a native image, test that separately:
java -version native-image --version ./mvnw -Pnative native:compile
For Vaadin apps, open the UI in a browser and check the main flows. A Boot migration can pass every backend smoke test and still ship broken styles.
Suggested upgrade sequence
- Upgrade to the latest approved Spring Boot 3.5.x release.
- Move to Java 21 if your target stack requires it, such as Vaadin 25.
- Remove deprecated APIs and deprecated configuration.
- Review unmanaged dependencies and remove unnecessary explicit versions.
- Align Spring Cloud and related Spring portfolio projects.
- Check Spring AI compatibility and decide whether to stay on Spring AI 1.1.x with Boot 3.5 or move to Spring AI 2.0.x with Boot 4.
- Run the Spring Boot 4 OpenRewrite recipe on a branch.
- Review the generated changes in small commits.
- Replace direct dependencies with Boot 4 starters where needed.
- Upgrade to the target Spring Boot 4.x release.
- For Vaadin apps, upgrade to Vaadin 25, migrate low-level JSON usage, load Lumo utility styles explicitly, rename Vaadin package scanning properties, and check Node.js 24 frontend builds.
- Add
spring-boot-properties-migrator, update configuration, and remove it. - Fix failures by category: dependencies, starters, JSON, Spring AI, Vaadin, security, web, data, tests, native.
- Run smoke tests against the packaged application or container image.
- Document repeated fixes for the next service.
The useful outcome
The immediate goal is a working Spring Boot 4 application. The better outcome is a cleaner service: fewer unmanaged dependencies, fewer deprecated assumptions, clearer starters, tested JSON contracts, and a deployment path that is known to work on the new baseline.
For teams migrating several services, do the first one slowly enough to make the second one boring. Keep the commands, dependency changes, and regression checks. The next service should feel less like an investigation and more like a checklist.
Further reading
- Spring Boot project page: https://spring.io/projects/spring-boot/
- Spring Boot upgrading docs: https://docs.spring.io/spring-boot/upgrading.html
- Spring Boot 4.0 Migration Guide: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-4.0-Migration-Guide
- OpenRewrite Spring Boot 4 recipe: https://docs.openrewrite.org/recipes/java/spring/boot4/upgradespringboot_4_0-community-edition
- Spring Cloud release train docs: https://docs.spring.io/spring-cloud-release/reference/spring-projects.html
- Spring AI getting started docs: https://docs.spring.io/spring-ai/reference/getting-started.html
- Spring AI upgrade notes: https://docs.spring.io/spring-ai/reference/upgrade-notes.html
- Vaadin 24 to 25 upgrade guide: https://vaadin.com/docs/latest/upgrading
- Vaadin Lumo Utility Classes: https://vaadin.com/docs/latest/styling/utility-classes
- Vaadin Spring configuration: https://vaadin.com/docs/latest/flow/integrations/spring/configuration
- Modularizing Spring Boot: https://spring.io/blog/2025/10/28/modularizing-spring-boot/
- Introducing Jackson 3 support in Spring: https://spring.io/blog/2025/10/07/introducing-jackson-3-support-in-spring/