API Versioning

Spring Boot 4 versions endpoints for you — no /v1/ copies of every controller, no manual header parsing.

Enable it

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

Other strategies:

PropertyClient sends
spring.mvc.apiversion.use.header=X-API-Versiona header
spring.mvc.apiversion.use.query-parameter=version?version=1.1
spring.mvc.apiversion.use.path-segment=1/api/1.1/books
spring.mvc.apiversion.use.media-type-parameter=…Accept: application/json;version=1.1

Reactive apps use spring.webflux.apiversion.*.

Version a mapping

@RestController
@RequestMapping("/api/books/{id}")
public class BookController {

    @GetMapping                      // any version — lowest priority
    Book get(@PathVariable long id) {  }

    @GetMapping(version = "1.1")     // exactly 1.1
    BookV1_1 get1_1(@PathVariable long id) {  }

    @GetMapping(version = "1.2+")    // 1.2 and everything above
    BookV1_2 get1_2(@PathVariable long id) {  }
}
curl -H "X-API-Version: 1.2" localhost:8080/api/books/1

Matching picks the highest version at or below the requested one. A request above every declared version fails with NotAcceptableApiVersionException → 400.

Tip

1.2+ is the useful default for new endpoints: you write the method once, and it keeps serving every later version until something actually changes.

Programmatic configuration

For several resolvers at once:

@Bean
WebMvcConfigurer apiVersioning() {
    return new WebMvcConfigurer() {
        @Override public void configureApiVersioning(ApiVersioningConfigurer c) {
            c.defaultVersion("1.0")
             .withHeaderResolver("X-API-Version")
             .withQueryParameterResolver("version");
        }
    };
}

Custom beans take over the details: ApiVersionResolver, ApiVersionParser, ApiVersionDeprecationHandler (for Deprecation / Sunset headers).

On the client side

RestClient and WebClient can send the version:

client.get().uri("/books/{id}", id)
      .apiVersion("1.2")
      .retrieve()
      .body(Book.class);

Versioning strategy

  • version the representation, not every internal change
  • additive changes (a new optional field) need no new version
  • removing or renaming a field does
  • announce deprecation before removal, and delete old versions on a schedule

★ Exercises

  1. Enable header-based versioning with a default of 1.0.
  2. Serve /api/books/{id} at 1.0 and a renamed field at 1.1+.
  3. Call both versions with curl and compare the JSON.
  4. Request version 9.9 — what status and body come back?
  5. Switch to query-parameter versioning without touching the controllers.