Skip to content
JSBlogs
Go back

API versioning in Spring Boot 4.x for safer REST upgrades

API changes are easy to introduce and hard to roll out safely. In Spring Boot 3.x and earlier, many teams handled versioning with custom filters, interceptors, or duplicated controller paths. That approach worked, but it often became inconsistent across services. Spring Boot 4.x adds built-in API versioning support, so you can standardize this with less custom code.

Table of contents

Open Table of contents

The problem with older versions (Spring Boot 3.x and earlier)

Before Spring Boot 4.x, versioning strategy was mostly team-defined. Some services used /v1/... and /v2/..., others used headers, and some mixed both. This made APIs harder to understand and harder to maintain.

Important: If each service versions APIs differently, client teams have to learn a new rule for every service.

Version detection logic was repeated

Teams often wrote their own logic to read versions from headers, query parameters, or media types. The same parsing and validation code got copied between services. Small differences in implementation caused unexpected behavior in production.

Controller mappings became noisy

A common pattern was duplicating request mappings for each version. That made controllers longer and harder to read. Over time, refactoring became risky because version-specific behavior was spread across many classes.

Deprecation handling was easy to forget

Older versions usually needed deprecation and sunset communication, but many teams handled this manually. Without a standard approach, clients got inconsistent guidance about when to migrate. This increased support load and slowed down deprecation plans.

Caution: Changing response fields without introducing a new API version can break frontend and mobile apps without obvious errors.

What Spring Boot 4.x changes

Spring Boot 4.x auto-configures the API versioning features introduced in Spring Framework 7. You can now declare a versioning strategy in configuration and bind endpoint versions directly in mapping annotations.

Configure API version resolution

Use application properties to define how the version is read. For example, this configuration uses a request header.

spring.mvc.apiversion.use.header=API-Version
spring.mvc.apiversion.default=1.0

For WebFlux applications, use the corresponding spring.webflux.apiversion.* properties.

Tip: Start with a single version source (header or path). Avoid mixing strategies unless you have a strong reason.

Bind versions directly in request mappings

You can declare versions on mapping annotations instead of building custom routing logic.

@RestController
@RequestMapping("/accounts")
class AccountController {

    @GetMapping(path = "/{id}", version = "1.0+")
    AccountV1 getAccountV1(@PathVariable long id) {
        return new AccountV1(id, "legacy-name");
    }

    @GetMapping(path = "/{id}", version = "2.0")
    AccountV2 getAccountV2(@PathVariable long id) {
        return new AccountV2(id, "full-name", "premium");
    }
}

record AccountV1(long id, String name) {}
record AccountV2(long id, String name, String tier) {}

Keep client requests consistent

Spring Framework 7 also adds support for setting API version metadata from clients in a consistent way.

RestClient client = RestClient.builder()
        .baseUrl("http://localhost:8080")
        .apiVersionInserter(ApiVersionInserter.useHeader("API-Version"))
        .build();

AccountV2 account = client.get()
        .uri("/accounts/{id}", 42)
        .apiVersion(2.0)
        .retrieve()
        .body(AccountV2.class);

Old way vs new way

AreaOlder versionsSpring Boot 4.x
Version routingCustom filters/interceptorsBuilt-in version-aware mappings
Controller readabilityOften duplicated pathsVersion is declared on mapping
Client callsManual header/query handlingStandard client version inserter support
Team consistencyService-by-service custom logicShared framework pattern

Practical tips for rollout

  1. Keep v1 stable while building v2.
  2. Add integration tests for each supported version.
  3. Return clear deprecation headers/messages for old versions.
  4. Decide a sunset date early and communicate it to consumers.

Note: Versioning solves compatibility problems, but it also adds maintenance cost. Keep only active versions and retire old ones on schedule.

Migration checklist from older projects

  1. Pick one version source first, usually a request header or path segment.
  2. Configure spring.mvc.apiversion.* (or spring.webflux.apiversion.*) in one place.
  3. Move version conditions from custom interceptors/filters into mapping annotations.
  4. Keep unchanged behavior on a baseline mapping such as "1.0+", then add strict mappings for new versions.
  5. Add integration tests that verify routing for each supported version.

References


Share this post on:

Previous Post
HTTP service clients in Spring Boot 4.x made simpler
Next Post
Introduction to Lombok (Speeding-up Java development)