Dev & EngARTICLE

Spring Boot 4.0: what breaks in auto-configuration and Jackson before you migrate your API

The official release notes, updated in September 2026, confirm Jackson 3.0 as the default and rename beans and properties used by anyone configuring observability and persistence. None of this shows up as an obvious error: it shows up as JSON or a metric that silently stopped matching.

Spring Boot 4.0: what breaks in auto-configuration and Jackson before you migrate your API
Image: Bisneto Braga

What's in the major bump

Spring Boot 4.0 isn't just a round number. The project's official release notes, revised in September 2026, list a long set of dependency upgrades: Spring Framework 7.0, Jakarta Servlet 6.1, Hibernate 7.2, Tomcat 11.0, Jetty 12.1 and, what matters most to anyone serializing JSON every day, Jackson 3.0. The team's own recommendation is painfully simple: upgrade to 3.5 first, stabilize, and only then move to 4.0.

That recommendation isn't bureaucracy. A Jackson major bump is the kind of change that doesn't show up in an error log: it shows up as a field that disappears from the JSON, a date that serializes differently, or a contract test that starts failing without anyone touching the domain code. That's where Spring Boot's auto-configuration comes in: it decides which ObjectMapper your project uses, and it changed.

Jackson 3: support for Jackson 2 is born with an expiration date

The central piece of information in the release notes sits quietly at the bottom of the page, in the deprecations section: support for Jackson 2 still exists in 4.0, but it already ships deprecated. In other words, it works, but it isn't where the project is heading. Anyone with third-party libraries that depend directly on com.fasterxml.jackson.databind version 2.x needs to check compatibility before pinning the Boot version.

In practice, this means three fronts of attention, as I read it:

  • Transitive dependencies: if some starter or internal library in your project forces a 2.x version of Jackson via dependencyManagement, it is now competing with the Jackson 3 that Boot brings by default.
  • Custom modules: Module, Serializer, and Deserializer written against the Jackson 2 API don't upgrade to 3 for free just because the jar is on the classpath.
  • Configuration via Jackson2ObjectMapperBuilder: class names carrying a "2" in the package tend to be the first candidates for formal deprecation in upcoming minor releases.

The release notes don't detail which serialization defaults change behavior between Jackson 2 and 3, and that, to me, is the strongest argument in favor of automated contract tests instead of visually inspecting the response JSON. A difference in date formatting or field ordering is exactly the kind of regression that goes unnoticed in a manual Pull Request review.

When @ConditionalOn* changes its name under the hood

The part most connected to the broken auto-configuration angle is scattered across the "Miscellaneous" section of the release notes, and it's easy to skip on a quick read. Several conditional annotations, properties, and auto-configuration classes were renamed for consistency, the kind of change the compiler flags immediately, but that application.yml swallows in silence.

Before (3.x)After (4.0)Where it usually hurts
@ConditionalOnEnabledTracing@ConditionalOnEnabledTracingExportCustom auto-configuration that extends Boot's
management.tracing.enabledmanagement.tracing.export.enabledProperty ignored without error, tracing disappears without warning
spring.dao.exceptiontranslation.enabledspring.persistence.exceptiontranslation.enabledSame: orphaned property in YAML
ScheduledTasksObservabilityAutoConfigurationScheduledTasksObservationAutoConfigurationBreaks the build for anyone importing the class directly

There's also a structural point, not a cosmetic one: public members (aside from constants) were removed from the auto-configuration classes. The project always made clear that these classes aren't public API, but in practice a lot of people extended or reused their methods. This breaks at compile time, which is a good thing: it's the kind of error you'd rather see in CI, not in production.

Testing the contract before switching versions

The most useful feature that 4.0 brings for anyone migrating isn't about serialization, it's about testing: RestTestClient. According to the documentation, it can be injected both in a @SpringBootTest with @AutoConfigureMockMvc (operating over the underlying MockMvc) and in a real integration test, with a fixed or random port, pointing at the actual server.

In a typical project, the contract test skeleton I'd write before switching the Boot version looks something like this:

java
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class PedidoApiContractTests {

    @Autowired
    RestTestClient restTestClient;

    @Test
    void retornaPedidoNoFormatoEsperado() {
        restTestClient.get().uri("/api/pedidos/42")
            .exchange()
            .expectStatus().isOk()
            .expectBody()
            .jsonPath("$.id").isEqualTo(42)
            .jsonPath("$.status").isEqualTo("CONFIRMADO")
            .jsonPath("$.criadoEm").exists();
    }
}

The value here isn't RestTestClient itself, it's the timing: run this suite against the 3.5 build (baseline) and then against 4.0, comparing field by field. A divergence in type, field name, or date format points exactly to where to investigate: Jackson, a renamed @ConditionalOn*, or a behavior change in some other component.

The checklist I'd run in a real migration

Following the project's own recommendation, the order that makes sense is:

  1. Upgrade to Spring Boot 3.5 first, if you're not there yet, and make sure the build and tests are green on that version as a baseline.
  2. Switch the Boot version to 4.0 (milestones have shipped directly on Maven Central since M1, without needing the repo.spring.io repository).
  3. Recompile and handle build errors first: direct use of auto-configuration classes that lost public members, @ConditionalOnEnabledTracing, and direct references to ScheduledTasksObservabilityAutoConfiguration.
  4. Run grep on application.yml/application.properties looking for management.tracing.enabled and spring.dao.exceptiontranslation.enabled, renaming them to the new keys.
  5. Check whether any dependency forces Jackson 2.x directly in pom.xml or build.gradle, since support still exists, but is deprecated.
  6. Run the contract test suite with RestTestClient against both versions and compare the JSON field by field, not just the HTTP status.

When it's not worth migrating yet

Not every project should move to 4.0 right now, and that's also an honest part of the checklist. If the API has little or no contract test coverage, migrating now means betting that nothing in the JSON changed, and the release notes themselves already warn that properties and classes were renamed across multiple fronts. If the project relies heavily on third-party libraries still tied to Jackson 2.x with no upgrade in sight, the safer path is to wait for the library to migrate first, not force the two to coexist.

Gradle 9 is already supported, but Gradle 8.14+ keeps working, so it isn't mandatory to switch both things in the same commit. Separating the build tooling update from the framework update reduces the error surface when something breaks: you at least know it wasn't Gradle.

Translated from the Brazilian Portuguese original · Read the original