What are common approaches to API versioning in Spring REST APIs?
Quick Answer
Common strategies include URI versioning (embedding the version in the path, e.g., /api/v1/orders — the simplest and most visible, but clutters the URL and complicates routing for shared resources), request header versioning (a custom header like Api-Version, keeping URLs stable), media type/content negotiation versioning (a custom Accept header value like application/vnd.company.v2+json), and query parameter versioning (?version=2). URI versioning is the most common in practice for its simplicity and discoverability, despite being the least 'RESTful' in a strict sense.
Detailed Answer
As a REST API evolves, breaking changes (renaming/removing a field, changing behavior) need a way to coexist with clients still using an older contract. Several strategies are common in Spring-based APIs:
1. URI versioning — embed the version directly in the path:
@RestController
@RequestMapping("/api/v1/orders")
class OrderControllerV1 { ... }
@RestController
@RequestMapping("/api/v2/orders")
class OrderControllerV2 { ... }
Simplest to implement and most discoverable/visible to API consumers (and easy to route, cache, and document per version), but clutters the URL, and strict REST purists argue a resource's URI shouldn't encode something as orthogonal as its representation's version.
2. Header versioning — a custom request header carries the version, URL stays stable:
@GetMapping(value = "/orders", headers = "Api-Version=2")
List<OrderV2> listOrdersV2() { ... }
Keeps URLs clean, but is less visible/discoverable (you can't just paste a URL into a browser to see a specific version) and slightly more effort to test/document.
3. Media type (content negotiation) versioning — the version lives inside a custom Accept header value:
@GetMapping(value = "/orders", produces = "application/vnd.company.app-v2+json")
List<OrderV2> listOrdersV2() { ... }
Considered the most "RESTful" by strict HATEOAS/content-negotiation advocates, but the least ergonomic for typical API consumers and tooling.
4. Query parameter versioning:
@GetMapping(value = "/orders", params = "version=2")
List<OrderV2> listOrdersV2() { ... }
Simple, but easy to omit accidentally (defaulting to some implicit version) and considered less clean than the path-based approach by many API design guides.
In practice: URI versioning is by far the most common choice for public and internal APIs alike, largely for its simplicity, visibility in logs/monitoring, and ease of routing at the infrastructure level (a load balancer or API gateway can trivially route /api/v1/* vs /api/v2/* to different backend deployments) — despite being the "least RESTful" option in a strict theoretical sense.