π§ Table of Contents
- π§ Table of Contents
- π Overview of API Versioning
- π» Common Java Beans Code
- 1. π URI Versioning
- 2. β Request Parameter Versioning
- π§ Custom Header Versioning
- 4. π¨ Content Negotiation (Media Type) Versioning
- π Comparative Analysis and Selection
- π API Gateway for Centralized Versioning and Transformation
- π‘Next Steps and Improvements
π Overview of API Versioning
API versioning is a crucial strategy for managing changes to an API over time without disrupting existing clients. Itβs necessary when introducing breaking changes (e.g., changing request/response formats, removing fields). A good versioning strategy ensures clients can conveniently upgrade to the latest version while clearly communicating changes.
Note
- Maven Dependency: No external dependency is required for implementing these HTTP architectural style versioning strategies in Spring Boot, as they utilize standard Spring Web annotations.
π» Common Java Beans Code
We use two versions of the Person class to demonstrate a breaking change in the data structure, where PersonV2 nests the name.
PersonV1.java bean
public class PersonV1 {
private String name;
public PersonV1(String name) {
super();
this.name = name;
}
// getter-setters omitted for brevity
}
PersonV2.java bean
public class PersonV2 {
private Name name;
public PersonV2(Name name) {
super();
this.name = name;
}
// getter-setters omitted for brevity
}
Name.java bean
public class Name {
private String firstName;
private String lastName;
public Name(String firstName, String lastName) {
super();
this.firstName = firstName;
this.lastName = lastName;
}
// getters-setters omitted for brevity
}
1. π URI Versioning
In this method, the API version number is embedded directly in the URL path. Itβs the most straightforward and common approach.
Enterprise Example:
Twitter/X is a well-known example that uses URL versioning strategy (
/v1,/v2, etc.).
Key Points
| Advantage: | Drawback: | Best Practice: |
|---|---|---|
| Cache-friendly (versions are distinct URLs), very easy to understand and implement. | Polluting the URL and requires code changes (new controller classes/methods) for every new version, leading to potential code duplication. | Use it for major, breaking changes. Align with Semantic Versioning (e.g., /v1, /v2). |
Controller Code changes (Java)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UriVersioningPersonController {
/**
* Version 1: GET /v1/person
*/
@GetMapping("/v1/person")
public PersonV1 getPersonV1() {
return new PersonV1("URI Versioning v1");
}
/**
* Version 2: GET /v2/person
*/
@GetMapping("/v2/person")
public PersonV2 getPersonV2() {
return new PersonV2(new Name("URI", "Versioning V2"));
}
}
Test API using CURL command
# Version 1
curl -X GET "http://localhost:8080/v1/person"
# Version 2
curl -X GET "http://localhost:8080/v2/person"
2. β Request Parameter Versioning
In this approach the API version is passed as a query parameter in the URL. The base URL remains constant across versions.
Enterprise Example:
Amazon is often cited as using this strategy for some of its services.
Key Points
| Advantage: | Drawback: | Best Practice: |
|---|---|---|
| Clean URI (base path is constant), easy to implement and test. | Polluting the URL with query parameters, less aesthetically pleasing, and not always considered RESTful as the version is not part of the resource identifier. | Best Practice: Useful when the client is a browser or when URI cleanliness is a priority, but less suitable for public-facing APIs. |
Controller Code changes (Java)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RequestParamVersioningController {
/**
* Version 1: GET /person/param?version=1
*/
@GetMapping(path = "/person/param", params = "version=1")
public PersonV1 getPersonV1() {
return new PersonV1("Request Param versioning v1");
}
/**
* Version 2: GET /person/param?version=2
*/
@GetMapping(path = "/person/param", params = "version=2")
public PersonV2 getPersonV2() {
return new PersonV2(new Name("Request Parama", "Versioning v2"));
}
}
Test API using CURL Command
# Version 1
curl -X GET "http://localhost:8080/person/param?version=1"
# Version 2
curl -X GET "http://localhost:8080/person/param?version=2"
π§ Custom Header Versioning
In this strategy the version information is conveyed via a custom HTTP header, avoiding changes to the URI.
Enterprise Example:
Microsoft (Azure APIs) has used custom headers for versioning.
Key Points
| Advantage: | Drawback: | Best Practice: |
|---|---|---|
| Clean URLs (most RESTful in terms of URI), and versions are invisible to generic API consumers unless they inspect the headers. | Drawback:Misuse of HTTP Headers (version is not standard HTTP concern), harder to test directly in a browser, and may require extra configuration for caching layers. | Best Practice: Use a clear header name like X-API-VERSION or Api-Version. Ensure all client libraries handle custom headers easily. |
Controller Code changes (Java)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class CustomHeaderVersioning {
/**
* Version 1: GET /person/header with header X-API-VERSION=1
*/
@GetMapping(path = "/person/header", headers = "X-API-VERSION=1")
public PersonV1 getPersonV1() {
return new PersonV1("Custom Header Versioning v1");
}
/**
* Version 2: GET /person/header with header X-API-VERSION=2
*/
@GetMapping(path = "/person/header", headers = "X-API-VERSION=2")
public PersonV2 getPersonV2() {
return new PersonV2(new Name("Custom Header", "Versioning v2"));
}
}
Test API using CURL command
# Version 1
curl -X GET "http://localhost:8080/person/header" -H "X-API-VERSION: 1"
# Version 2
curl -X GET "http://localhost:8080/person/header" -H "X-API-VERSION: 2"
4. π¨ Content Negotiation (Media Type) Versioning
Also known as Accept Header Versioning. The version is specified within the Accept HTTP header using a custom media type (e.g., application/vnd.comp.app-v1+json).
Enterprise Example:
GitHub is a prominent example utilizing this strategy.
Key Points
| Advantage: | Drawback: | Best Practice: |
|---|---|---|
| Most RESTful approach according to some purists, as it uses standard HTTP negotiation mechanisms. Clean URLs. | Drawback:Complex to implement and can be confusing for developers. Misuse of headers, similar to custom header approach. | Best Practice: Follow the standard format: application/vnd.<vendor>.<resource>-v<version>+json/xml. Use it when resource representation changes fundamentally. |
Controller Code changes (Java)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MediaTypeVersioning {
/**
* Version 1: GET /person/accept with Accept: application/vnd.comp.app-v1+json
*/
@GetMapping(path = "/person/accept", produces = "application/vnd.comp.app-v1+json")
public PersonV1 getPersonV1() {
return new PersonV1("Mediatype Versioning v1.");
}
/**
* Version 2: GET /person/accept with Accept: application/vnd.comp.app-v2+json
*/
@GetMapping(path = "/person/accept", produces = "application/vnd.comp.app-v2+json")
public PersonV2 getPersonV2() {
return new PersonV2(new Name("Media type", "Versioning v2"));
}
}
Test API using CURL Command
# Version 1
curl -X GET "http://localhost:8080/person/accept" -H "Accept: application/vnd.comp.app-v1+json"
# Version 2
curl -X GET "http://localhost:8080/person/accept" -H "Accept: application/vnd.comp.app-v2+json"
π Comparative Analysis and Selection
Choosing the right approach depends on your APIβs audience, update frequency, and caching needs.
| Strategy | Advantages | Drawbacks | Best For |
|---|---|---|---|
| URI | Highly visible, cache-friendly, simple. | Pollutes URL, high maintenance (code duplication). | Public APIs, major breaking changes. |
| Request Param | Clean URI base, simple to implement. | Pollutes URL (query string), less RESTful, not cache-friendly by default. | Internal APIs, simple versioning needs. |
| Custom Header | Clean URLs, version is out of the URI. | Not browser-testable, potential misuse of headers, complex caching. | APIs with frequent, minor updates. |
| Media Type | Most RESTful, clean URLs, resource-level versioning. | Most complex to implement, developer confusion, complex caching. | APIs with strong REST adherence, internal services. |
Strategy to Choose API Versioning Approach
The choice of API versioning strategy should be made early in the design phase and should be based on your APIβs audience, caching needs, and the frequency/complexity of anticipated changes.
| Versioning Strategy | Primary Audience | REST Purity | Caching | Implementation Complexity | URL Cleanliness |
|---|---|---|---|---|---|
| π URI | Public/External | Low (Pollutes URL) | High (Natively Cacheable) | Low (Simple @GetMapping) | Low (Version in URL) |
| Query Parameter | Internal/Simple APIs | Low (Uses Query Params) | Low (Requires Custom Config) | Low (Simple params attr) | Medium (Base URL clean) |
| π§ Custom Header | Partner/Internal | Medium (Custom Header) | Medium (Requires Header-Aware Config) | Medium (Uses headers attr) | High (Cleanest URL) |
| π¨ Media Type | Strong REST Adherence | High (Standard HTTP Negotiation) | Medium (Requires Content-Aware Config) | High (Custom Media Type String) | High (Cleanest URL) |
π API Gateway for Centralized Versioning and Transformation
An API Gateway acts as a single entry point for all client requests, centralizing cross-cutting concerns like security, rate limiting, and crucially, API Versioning.
Gatewayβs Role in Centralizing Versioning
- Centralized Routing: The gateway inspects the incoming request (URI path, query param, or header) and routes it to the correct microservice version. This shields the client from knowing the internal microservice deployment topology.
- Decoupling: It decouples the external API contract (the version clients see) from the internal microservice implementation version.
Role in Transformation (Non-Breaking Change Scenario)
For a non-breaking change (e.g., adding an optional field in V2, like splitting name into firstName and lastName), the Gateway can simplify client migration:
- V2 Request to V1 Microservice: * If a V2 client sends a request that is backward compatible with V1, the Gateway can add default values for the new V2 fields in the request body before routing it to the V1 service.
- V1 Response Transformation: * If a V1 client calls the endpoint, the Gateway routes the request to the new V2 microservice. The V2 service responds with the new, richer V2 data structure (
firstName,lastName). The Gateway then performs a response transformation (e.g., concatenatingfirstNameandlastNameback into a singlenamefield) to match the expected V1 schema before sending it back to the V1 client.
This transformation capability allows the backend microservice to evolve faster (implementing V2) while the Gateway maintains backward compatibility for older V1 clients without requiring a full parallel V1 and V2 deployment.
sequenceDiagram
participant C as Client
participant G as API Gateway
participant S2 as Microservice V2
participant S1 as Microservice V1
C->>G: Request V1 (GET /person/header, X-API-VERSION=1)
alt Response Transformation for Non-Breaking Change
Note over G: Route to latest version S2
G->>S2: Transform Request (if needed) & Forward
S2->>G: Response V2 (with firstName, lastName)
Note over G: Transform Response (concat to name) to V1 Schema
G->>C: Response V1 (with name)
else Breaking Change
Note over G: Route to appropriate version S1
G->>S1: Forward Request V1
S1->>G: Response V1
G->>C: Response V1
end
π‘Next Steps and Improvements
- Documentation Tooling:
- Integrate an API documentation tool like OpenAPI (Swagger).
- Ensure the documentation tool clearly shows how to access each version (via URI, header, or parameter).
- Custom Annotations:
- For cleaner controller code, consider creating a custom Spring annotation (e.g.,
@ApiVersion(version = "1")) that internally handles the different versioning strategies.
- For cleaner controller code, consider creating a custom Spring annotation (e.g.,
This video provides a deep dive into implementing various API versioning strategies in Spring Boot. API Versioning In Spring Framework 7 by Rossen Stoyanchev @ Spring I/O 2025