You will be redirect to our new portal geekmonks.com in 10, Happy Learning. Click here to redirect now.

πŸš€ Microservice - API Versioning Strategies

Updated on: 09 Nov 2024 - Vivek Singh


🧭 Table of Contents


πŸš€ 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.

StrategyAdvantagesDrawbacksBest For
URIHighly visible, cache-friendly, simple.Pollutes URL, high maintenance (code duplication).Public APIs, major breaking changes.
Request ParamClean URI base, simple to implement.Pollutes URL (query string), less RESTful, not cache-friendly by default.Internal APIs, simple versioning needs.
Custom HeaderClean URLs, version is out of the URI.Not browser-testable, potential misuse of headers, complex caching.APIs with frequent, minor updates.
Media TypeMost 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 StrategyPrimary AudienceREST PurityCachingImplementation ComplexityURL Cleanliness
πŸ”— URIPublic/ExternalLow (Pollutes URL)High (Natively Cacheable)Low (Simple @GetMapping)Low (Version in URL)
Query ParameterInternal/Simple APIsLow (Uses Query Params)Low (Requires Custom Config)Low (Simple params attr)Medium (Base URL clean)
πŸ“§ Custom HeaderPartner/InternalMedium (Custom Header)Medium (Requires Header-Aware Config)Medium (Uses headers attr)High (Cleanest URL)
🎨 Media TypeStrong REST AdherenceHigh (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:

  1. 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.
  2. 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., concatenating firstName and lastName back into a single name field) 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.


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