← Back to portfolio

Spring Boot 4 in Production: What Changed and What Broke

Spring BootJavaMigrationProduction

Building projects on Spring Boot 4 reveals meaningful changes from Spring Boot 3. Here is what changed, what broke during development, and what teams need to know before upgrading.

What Changed

Jakarta namespace is finalized. Spring Boot 3 started the javax to jakarta migration. Spring Boot 4 completes it. Any remaining javax.servlet or javax.validation imports must be updated.

Hibernate 7. The ORM upgrade brings performance improvements but changes some default behaviors. ddl-auto: validate is stricter: it validates column types, not just table existence. If Flyway migrations create columns with slightly different types than what Hibernate expects, validation fails.

WebClient.Builder is no longer auto-configured. In SB3, injecting WebClient.Builder just worked. In SB4, the builder bean must be created explicitly or WebClient.builder() used directly. This breaks clients for external services on first deploy.

ObjectMapper requires explicit module registration. The spring-boot-starter-webmvc no longer transitively includes Jackson's Java Time module. Adding spring-boot-starter-json and explicit JavaTimeModule registration is required. This causes serialization failures that can take time to diagnose because the error message does not mention the missing module.

What Broke

  1. Every WebClient.Builder injection failed with "No qualifying bean."
  2. Instant serialization failed because jackson-datatype-jsr310 was not on the classpath.
  3. Hibernate validation failed because Flyway migrations were not running before schema validation.
  4. Spring Security's filter chain required explicit permitAll() configuration.

The Fixes

All four issues have straightforward fixes once identified. The lesson: when upgrading Spring Boot major versions, deploy to a test environment first and exercise every API endpoint. The failures are runtime, not compile-time. Tests might pass (they use H2 and embedded servers) while the Docker container fails.