Skip to main content

How to Upgrade Spring Boot Without Breaking Your APIs

· 10 min read
Evgenii Frolikov
Senior Java Architect | Expert in High-Load Systems & JVM Internals

How to Upgrade Spring Boot Without Breaking Your APIs

TL;DR: Bumping the Spring Boot version in pom.xml takes 10 seconds. Finding out that the upgrade silently changed your JSON serialization, broke your security filters, or altered your error responses takes days. This post covers seven categories of silent breaking changes that Spring Boot upgrades introduce, and a practical workflow to catch them before production.

Why Spring Boot Upgrades Are Deceptively Risky

Spring Boot upgrades feel safe. The changelog says "bug fixes and dependency updates." The compiler reports zero errors. The unit tests pass. The integration tests are green.

Then production starts behaving differently.

The problem is that Spring Boot is an opinionated framework. It makes hundreds of auto-configuration decisions based on the dependencies on your classpath: which ObjectMapper features are enabled, which security filters are active, how errors are formatted, which HTTP message converters are registered. When you upgrade Spring Boot, these decisions can change.

These changes are not bugs. They are intentional improvements. But they are invisible to your test suite because your tests verify business logic, not framework configuration side effects.

Seven Categories of Silent Breaking Changes

1. Jackson Serialization Defaults

Spring Boot auto-configures ObjectMapper with a set of modules and features. A version bump can change:

  • Date format. JavaTimeModule behavior changes between Jackson minor versions. LocalDateTime can flip between ISO string and Unix timestamp.
  • Enum serialization. Default can switch between name and ordinal depending on jackson-databind version.
  • Null handling. Include.NON_NULL vs Include.ALWAYS can change if a dependency's Jackson2ObjectMapperBuilderCustomizer is added or removed.
  • Module registration order. New modules on the classpath (e.g., jackson-datatype-jdk8) alter Optional serialization.

What breaks: Every outgoing HTTP request and response that uses JSON serialization. The Java objects are unchanged. The actual bytes over the wire are different.

How to detect: Compare the serialized JSON payloads of your API endpoints before and after the upgrade. See our deep dive into Jackson configuration changes.

2. Spring Security Filter Chain

Spring Security changes its defaults between minor versions more aggressively than most teams expect:

  • CSRF protection. New versions may enable CSRF for endpoints that previously had it disabled.
  • Session management. Default session creation policy can change from IF_REQUIRED to STATELESS or vice versa.
  • Authorization rules. The migration from WebSecurityConfigurerAdapter (removed in Spring Security 6.0) to the SecurityFilterChain bean model can subtly change rule evaluation order.
  • Default headers. X-Content-Type-Options, X-Frame-Options, Cache-Control headers may be added or changed.

What breaks: Requests that used to succeed start returning 403 Forbidden or 401 Unauthorized. Or previously protected endpoints become unprotected.

How to detect: Trigger authenticated and unauthenticated requests to every protected endpoint. Compare response status codes and security-related headers before and after.

3. Error Response Format

Spring Boot's DefaultErrorAttributes and BasicErrorController evolve between versions:

  • Field names. The timestamp format, the presence of trace, the structure of errors array can all change.
  • Status mapping. Which exceptions map to which HTTP status codes can change when ResponseStatusException handling is updated.
  • Content negotiation. Error responses might return JSON in one version and HTML in another, depending on the Accept header handling.

What breaks: Upstream services that parse error responses. Frontend applications that display error messages. Monitoring systems that parse error payloads. See our deep dive into error contract regressions.

How to detect: Trigger error scenarios (invalid input, non-existent resources, unauthorized access) and compare the error response format before and after.

4. Actuator Endpoint Changes

Spring Boot Actuator endpoints change frequently between versions:

  • Endpoint paths. The default base path and individual endpoint paths can change.
  • Response format. Health check response structure, metrics format, and info endpoint content can all change.
  • Exposure defaults. Which endpoints are exposed over HTTP vs JMX changes between versions.
  • Security defaults. Actuator endpoint security configuration can change, especially after Spring Security upgrades.

What breaks: Monitoring dashboards, health check probes in Kubernetes (livenessProbe / readinessProbe), CI/CD pipeline health checks, and alerting rules that parse actuator responses.

How to detect: Call all actuator endpoints you use in production (especially /actuator/health) and compare response format and accessibility before and after.

5. HTTP Message Converter Order

Spring Boot registers HTTP message converters in a specific order. The order determines which converter handles a request based on Content-Type and Accept headers:

  • XML vs JSON priority. Adding or removing jackson-dataformat-xml from the classpath can change the default response format.
  • Form data handling. FormHttpMessageConverter behavior can change, affecting multipart/form-data processing.
  • String converter. StringHttpMessageConverter charset defaults can change (UTF-8 vs ISO-8859-1).

What breaks: API responses that suddenly return XML instead of JSON (or vice versa). File upload endpoints that stop parsing multipart requests. Character encoding issues in non-ASCII text.

How to detect: Send requests with explicit Accept headers and without them. Compare response Content-Type and body encoding before and after.

6. Database and JPA Behavior

Spring Boot manages Hibernate and Spring Data JPA versions:

  • Hibernate dialect. Auto-detected dialect can change between Hibernate versions, altering SQL production.
  • DDL auto. Default spring.jpa.hibernate.ddl-auto behavior can change.
  • Naming strategy. Physical and implicit naming strategies can change, causing column name mismatches.
  • Lazy loading defaults. Proxy initialization behavior can change, introducing LazyInitializationException in previously working code.
  • Query creation. HQL/JPQL to SQL translation can change, producing different queries with different performance characteristics.

What breaks: SQL queries that suddenly fail or return different results. Performance regressions from changed query plans. Column name mismatches after naming strategy changes.

How to detect: Capture and compare the actual SQL queries executed for key business scenarios. Compare query count, query text, and execution time before and after.

7. Embedded Server and HTTP Handling

Tomcat, Jetty, or Undertow versions bundled with Spring Boot change behavior:

  • Header handling. Maximum header size, header name case sensitivity, and duplicate header handling can change.
  • Request size limits. Default max-http-form-post-size and multipart limits can change.
  • Connection timeouts. Default idle timeout, connection timeout, and keep-alive behavior can change.
  • URL encoding. Path parameter encoding and special character handling can change.

What breaks: Large file uploads that suddenly fail. Requests with many headers (e.g., JWT tokens) that are rejected. Long-running requests that time out. URLs with special characters that stop working.

How to detect: Test with edge cases: large payloads, long headers, special characters in URLs, slow responses.

The Upgrade Verification Workflow

A safe Spring Boot upgrade follows this workflow:

Step 1: Capture Baseline Traces

Before changing the version, run your key business scenarios with BitDive agent attached. Capture traces that cover:

  • Happy path for all critical API endpoints
  • Error scenarios (invalid input, not found, unauthorized)
  • Authenticated and unauthenticated requests
  • Actuator endpoints used by your infrastructure

Each trace captures the full HTTP exchange: request headers, request body, response status, response headers, response body, and all downstream calls with their payloads.

Step 2: Bump the Version

Update spring-boot-starter-parent (or spring-boot-dependencies BOM) in your pom.xml:

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version> <!-- was 3.1.5 -->
</parent>

Run mvn dependency:tree and review the transitive dependency changes. Pay special attention to:

  • jackson-databind version
  • spring-security-* versions
  • hibernate-core version
  • Embedded server version (Tomcat, Jetty, Undertow)

Step 3: Compile and Run Existing Tests

Fix any compilation errors. Run the existing test suite. Fix any test failures.

This step catches the obvious breaks. The danger is assuming that "tests pass = upgrade is safe."

Step 4: Capture New Traces

Restart the service with the new version and BitDive agent. Trigger the same scenarios from Step 1. Capture new traces.

Step 5: Compare Traces

Diff the baseline and new traces across every layer:

What to compareWhy
Response body JSONJackson serialization changes
Response headersSecurity header changes, Content-Type changes
Error response formatDefaultErrorAttributes changes
Downstream request payloadsSerialization of outgoing calls
SQL queriesHibernate/JPA query production changes
Actuator response formatHealth check and metrics changes
Response status codesSecurity filter and error mapping changes

Step 6: Classify Differences

Not every diff is a problem:

Expected (normal upgrade behavior):

  • New security headers added by Spring Security
  • Actuator response includes new fields
  • Minor SQL query syntax changes with same semantics

Critical (likely regression):

  • JSON field serialization format changed
  • Response status code changed
  • Error response body structure changed
  • Security filter blocks previously allowed requests
  • Downstream API payload changed
  • SQL query count increased (N+1 introduced)

For a real example of layer-by-layer trace comparison, see the Interactive Demo with Cursor (video).

A Practical Checklist

Use this checklist for every Spring Boot version upgrade:

  • Compare mvn dependency:tree before and after
  • Verify Jackson serialization output (dates, enums, nulls, BigDecimal)
  • Test authenticated + unauthenticated access to protected endpoints
  • Trigger error scenarios and compare error response format
  • Verify actuator endpoint responses (especially /health)
  • Check response Content-Type headers (JSON vs XML vs HTML)
  • Verify downstream API call payloads are unchanged
  • Compare SQL query count and query text for key scenarios
  • Test file uploads and multipart requests
  • Check request size limits and header size limits

Verify Spring Boot Upgrades with Real Traces

BitDive captures HTTP exchanges and SQL queries from real API calls. Compare traces before and after a framework upgrade to catch serialization changes, security filter regressions, and error format drift.

Try BitDive Free

FAQ

How often should I upgrade Spring Boot?

Stay within the latest patch version of your current minor release (e.g., 3.1.x). Upgrade to the next minor release (3.1 → 3.2) within 3-6 months. Each minor release is supported for about 12 months. Delaying upgrades beyond the support window means missing security patches.

Is the Spring Boot migration guide enough?

The official migration guide covers intentional API changes. It does not cover transitive dependency behavior changes (Jackson, Hibernate, Tomcat), auto-configuration side effects, or the interaction between multiple changed defaults. The guide is necessary reading but not sufficient for production safety.

Can I automate this workflow in CI/CD?

Yes. Capture baseline traces as part of your release pipeline. After a version bump PR, run the same scenarios and compare automatically. BitDive's trace comparison shows a clear diff report. If any critical-layer diff is detected, the pipeline can flag the PR for manual review.


Try BitDive Free