๐Ÿค” Should You Upgrade Now? Link to heading

Spring Boot 4.0 shipped on November 20, 2025, on top of Spring Framework 7.0. The release notes already list what changed. What they can’t tell you is whether it’s worth doing now โ€” and in what order.

For Kotlin teams, my answer is yes, and it comes down to one change: Spring’s APIs now carry JSpecify nullability annotations, which Kotlin turns into real nullable and non-null types. The Spring team’s null-safety post put it simply: “Goodbye platform types, including for generics.” A Spring API that used to hand you a String! now hands you a String or a String?, and the compiler holds you to it.

Platform types were always the hole in Kotlin’s null safety at the framework boundary. That hole is mostly closed โ€” and if you picked Kotlin for null safety, that’s the payoff.

The more Spring calls a service makes, the more it gains. A service in maintenance mode with thin tests can wait a little โ€” its first job is writing the tests that’ll catch the upgrade’s surprises.

Waiting has a cost, though. Spring’s support timeline lists open-source support for 3.5.x ending on June 30, 2026. And don’t hold out for 4.1 (its first RC landed on April 23, the same day as 4.0.6) โ€” most of the work is the jump from 3.x to 4.x, not the minor version.


๐Ÿ’ธ The Three Things That Will Actually Cost You Time Link to heading

๐Ÿง  1) Nullability errors Link to heading

Kotlin treats JSpecify mismatches as errors by default. Spring’s release notes give three reasons your code may need changes: some Java APIs changed nullability, Kotlin reads JSpecify differently from JSR 305, and Spring’s Kotlin extensions were refined to match the Java APIs โ€” the JdbcOperations.queryForObject and queryForList extensions that took an Array<out Any> now take a vararg, for example.

You’ll get errors wherever you passed a nullable value into a non-null parameter or treated a nullable return as non-null. Most fixes are small. There can be a lot of them.

One Boot-specific spot: Actuator endpoint parameters need org.jspecify.annotations.Nullable instead of org.springframework.lang.Nullable.

๐Ÿ“ฆ 2) Jackson 3 Link to heading

Jackson 3 moves from com.fasterxml.jackson to tools.jackson โ€” except jackson-annotations, so @JsonProperty stays put. The Kotlin module moves with it:

// build.gradle.kts, Boot 3
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")

// build.gradle.kts, Boot 4
implementation("tools.jackson.module:jackson-module-kotlin")

The coordinates are the easy part. The changed defaults are what hurt: dates now serialize as ISO-8601 strings instead of timestamps, and properties are sorted alphabetically. Any test that compares raw JSON strings is at risk, and so is any client that depended on the old shape.

Also watch for this: Boot now auto-configures a JsonMapper, so your own ObjectMapper bean no longer replaces it. And Jackson2ObjectMapperBuilderCustomizer, @JsonComponent and @JsonMixin became JsonMapperBuilderCustomizer, @JacksonComponent and @JacksonMixin.

๐Ÿงช 3) Starters and tests Link to heading

Boot 4 splits its auto-configuration into modules with their own starters. spring-boot-starter-web is deprecated in favor of spring-boot-starter-webmvc, and Flyway or Liquibase now need spring-boot-starter-flyway or spring-boot-starter-liquibase.

Tests take a hit too. @MockBean and @SpyBean are gone (use @MockitoBean and @MockitoSpyBean), @SpringBootTest no longer sets up MockMvc without @AutoConfigureMockMvc, and TestRestTemplate needs @AutoConfigureTestRestTemplate plus its own dependency.


๐Ÿ›ค๏ธ The Path Link to heading

Each step is a PR you can merge and ship on its own โ€” except 3 and 4, which travel together.

1. Get to the latest 3.5.x with zero deprecation warnings. The migration guide recommends it, and 4.0 removes everything deprecated in 3.x โ€” including @MockBean, deprecated since 3.4, so swap to @MockitoBean now. Check every dependency Boot doesn’t manage (Spring Cloud especially) for a 4.x-compatible version, and make sure your JSON contract tests assert the real output, dates and property order included.

2. Move to Kotlin 2.2 or later, and add the flag Spring Boot’s Kotlin docs recommend:

// build.gradle.kts
kotlin {
    compilerOptions {
        freeCompilerArgs.addAll("-Xannotation-default-target=param-property")
    }
}

It opts into Kotlin 2.2’s new annotation rules, so an annotation such as @Email on a constructor property applies to the parameter and the field. In my HTTP validation post I said you need @field:NotBlank for Bean Validation to see it. That still works, but now a bare annotation that was being ignored may start validating. Run your validation tests on this PR.

3. Bump Boot on the classic starters, and move Jackson. The migration guide’s spring-boot-starter-classic and spring-boot-starter-test-classic put all the auto-configuration back on the classpath, so you can fix imports first and trim dependencies later. Swap the Jackson module to tools.jackson.module, and set spring.jackson.use-jackson2-defaults=true to keep your JSON close to Boot 3’s shape. Skip spring-boot-jackson2: it ships deprecated, and Spring Framework plans to stop auto-detecting Jackson 2 in 7.1 and remove support in 7.2.

4. Fix the nullability errors. This is the bulk of the work. To see the whole list at once, downgrade them to warnings โ€” on the branch only:

kotlin {
    compilerOptions {
        // temporary: report JSpecify mismatches as warnings instead of errors
        freeCompilerArgs.add("-Xjspecify-annotations=warn")
    }
}

Fix them, then delete the flag before merging. The compiler’s telling you something true now.

5. Swap to the modular starters and fix the tests. Replace the classic starters with the real ones, add @AutoConfigureMockMvc where tests use MockMvc, and sort out TestRestTemplate (or try the new RestTestClient).

6. Run spring-boot-properties-migrator once. Add it as a runtime dependency, start the app, read the log for renamed properties, then remove it.

7. Remove every stop-gap. Drop use-jackson2-defaults, decide which Jackson 3 defaults your clients can live with, and run the contract tests. Ship this one by itself โ€” it’s the step most likely to change what clients see.


โœ… How You Know You’re Done Link to heading

Start with a search: starter-classic, use-jackson2-defaults, properties-migrator and -Xjspecify-annotations shouldn’t appear anywhere in the build.

Then trust the tests that check behavior at the edges โ€” JSON contract tests, request-validation tests, and the MockMvc tests you just repaired. Those are where Boot 4’s changes surface.

In staging, run the 4.x build next to the 3.5 build and compare startup, memory, and error rates. Try Java 25 there too if you can; Spring Framework 7 recommends it, and it’s the current LTS.

Rollback is where the small PRs pay off. You revert newest-first, so backing out the Boot bump doesn’t mean undoing the Kotlin upgrade or the 3.5 cleanup. Keep the last 3.5 build deployable until 4.x has run in production for a while.


๐ŸŽ Everything Else Link to heading

Once you’re there, some nice things are waiting: built-in API versioning (@GetMapping(version = "2.0+")), HTTP service clients registered with @ImportHttpServices and a per-group base URL, a real kotlinx.serialization starter (spring-boot-starter-kotlinx-serialization-json, renamed in 4.0.1), and tracing context that follows suspending functions โ€” handy if you’re on coroutines, which I compared in Coroutines, Reactor, or Virtual Threads?. One catch โ€” Boot no longer manages a Spring Retry version, so pin one if you still use it. The release notes cover the rest.


โ˜• Conclusion Link to heading

Spring Boot 4 is the best release for Kotlin developers in a long time โ€” not because of one feature, but because Spring’s APIs finally tell the Kotlin compiler the truth about nulls.

The cost is upfront, and it’s mostly a long list of small fixes. Take it one mergeable step at a time, keep your contract tests close, and the jump is a lot less scary than the release notes make it look. Fill the red mug before you start โ€” the compiler has a few things to tell you.


Sources


Related Posts