I ran the same Spring Petclinic upgrade path from Boot 2.7.3 to 4.1.2 three times. It finished green each time.
The starting code was the public sample at commit 9ecdc11, with Java 8 in the POM. The whole recorded run took about 26 minutes. Writing tests against the running app came first and took 9m48s of that.
The timings are from one sample run.
The Boot 2 to 3 guide and Boot 3 to 4 guide cover the individual migrations. This is the sequence that was actually used across both.
The 11 steps
Test preparation is step 1 here. Every upgrade step gets its own commit. The times include the agent’s work plus the build and tests at that step.
| Step | Change | Recorded time |
|---|---|---|
| 1 | Write HTTP tests on the original 2.7.3 app | 9m48s |
| 2 | Patch 2.7.3 to 2.7.18; change mysql-connector-java to com.mysql:mysql-connector-j | Not recorded separately |
| 3 | Move Java 8 to 17 on 2.7; update JaCoCo and add explicit JAXB dependencies | 1m10s |
| 4 | Move 2.7 to 3.0.13; Jakarta packages and plugin coordinates | 2m02s |
| 5 | Move 3.0 to 3.2.12; compiler plugin settings | 1m08s |
| 6 | Move 3.2 to 3.4.13; preserve the POST redirect assertions | 1m09s |
| 7 | Replace @MockBean and @SpyBean in 4 test classes | 1m25s |
| 8 | Move 3.4 to 3.5.16 | 1m07s |
| 9 | Move 3.5 to 4.0.9, part 1, using classic starters | 2m50s |
| 10 | Finish 4.0 modular starter changes, part 2 | 1m55s |
| 11 | Move 4.0 to 4.1.2 | 1m18s |
Write the HTTP tests on the starting version
The tests start the real app on a random port and use TestRestTemplate against the real database. For writes, they send a fresh unique value through the endpoint, then retrieve it through a different GET of the running app.
The later version changes have existing assertions to satisfy.
I checked those assertions by removing the save from each of Petclinic’s five write handlers in separate copies. The HTTP tests caught all five broken endpoints on the original 2.7.3 code. Petclinic’s own mocked controller tests caught none.
The same experiment on 4.1.2 had the same result. The mutation experiment includes the handler and test code, plus the per-endpoint failure counts.
The assertions stayed unchanged and ran after every step. Boot 4 required an import move and one annotation.
Patch Boot and change Java before the major jump
First, 2.7.3 became 2.7.18, the latest 2.7 patch recorded in this path. That step also changed the MySQL dependency from mysql-connector-java to com.mysql:mysql-connector-j.
Java 8 then became Java 17 while the application was still on Boot 2.7. JaCoCo moved from 0.8.7 to 0.8.8. JAXB dependencies became explicit because JAXB left the JDK in Java 11.
Keep the patch and Java changes in separate commits. Both get a passing build before the 3.0 migration.
The first risky jump: 2.7 to 3.0
The move to 3.0.13 changed 16 files. It included the javax to jakarta work, such as javax.persistence.Entity, Table, and Column moving to jakarta.persistence.*.
The JAXB artifact changed from jaxb-api to jakarta.xml.bind-api. The Git commit plugin moved to io.github.git-commit-id:git-commit-id-maven-plugin.
These changes took 2m02s with the build and tests.
The Jakarta EE migration guide covers the namespace and dependency changes. The Java 17 build was already passing before these edits.
The smaller steps still need the tests
From 3.0 to 3.2.12, the compiler plugin settings changed. java.source and java.target came from ${java.version} instead of maven.compiler.*.
The intermediate Boot steps generally changed 1 to 3 files each. The tests still caught a behavior change in 3.4.
After 3.2 to 3.4.13, two HTTP tests expected a POST redirect and saw a 200. Boot 3.4 auto-detected the JDK HttpClient, which followed the 302 to the next page.
The verified setting on 3.4 was:
spring.http.client.factory=simple
That preserved the pre-3.4 client behavior for these assertions.
Then @MockBean and @SpyBean became @MockitoBean and @MockitoSpyBean in 4 test classes. Those replacements were made before 4.0, while the older annotations were deprecated in 3.4 rather than already removed.
After that separate commit, the app moved to 3.5.16. The test error notes give the exact package names and redirect failure string.
The second risky jump: 3.5 to 4.0
This jump involved the module split across 16 files. It was done in two parts.
Part 1 added spring-boot-starter-classic and spring-boot-starter-test-classic as a stepping stone to a compiling 4.0.9 application.
Part 2 replaced the classic starters with modular dependencies. The web starter became spring-boot-starter-webmvc. The test-scoped additions were spring-boot-starter-webmvc-test and spring-boot-starter-data-jpa-test, plus spring-boot-resttestclient and spring-boot-starter-restclient.
The redirect setting also needed its Boot 4 name:
spring.http.clients.imperative.factory=simple
Before the moved-class problem was handled, one run got stuck here because an agent guessed package names. LocalServerPort was moved to a package that doesn’t exist. Its correct import hadn’t changed.
There was also a duplicate vets cache failure caused by differing test context configurations. That stopped tests before they sent requests. Adding @AutoConfigureTestRestTemplate to PetClinicIntegrationTests as well as the HTTP test classes was the verified fix.
Finish on 4.1 with the same behavior checks
The last step moved 4.0 to 4.1.2 and took 1m18s, including the build and tests. The path finished green three times after the moved-class problem was handled.
For the mutation checks, each broken copy ran the whole suite with:
./mvnw test
Both versions passed unmodified. Removing any one of the five saves failed the matching HTTP tests.
Have the write assertions running before the first version edit. Keep each step in its own passing commit so a new failure has a small set of changes to explain.
Related Articles
- Spring Boot 2.7 to 3.x Migration Guide
- Spring Boot 3 to 4 Migration Guide
- Jakarta EE Migration for Spring Boot 3
- Spring Boot 4 Test Errors
Java Modernization Readiness Assessment
15 questions your team should answer before starting a migration. Takes 10 minutes. Could save you months.