Mastering API Versioning in Spring Boot: URI vs Header vs Media-Type

DevanshuDevanshu
3 min read

"If your API doesn’t evolve, your product probably isn’t either."

In the real world, breaking changes to your APIs are often unavoidable. The key is how you handle them. That’s where API versioning comes in — ensuring that older clients still work, even as your backend evolves.

This comprehensive guide dives deep into three robust ways to implement API versioning in Spring Boot:

  1. URI Path Versioning

  2. Header Versioning

  3. Media-Type Versioning


? Why API Versioning is Essential

APIs are contracts. Changing them without notice breaks clients, results in poor user experience, and often leads to production issues. Versioning is your insurance policy against this chaos.

👍 Versioning helps:

  • Prevent breaking existing clients when APIs change

  • Introduce new features safely

  • Support legacy systems during migration

  • Maintain multiple API versions simultaneously

Let’s take a real-world example:

You initially exposed a GET /users API returning the user's name and country. Now you want to include their job role as well.

You now have two choices:

  • Break the response format (bad)

  • Version the API (best practice)

Let's see how you'd handle this change across the different strategies.

The Original Version - V1

UserV1

public class UserV1 {
    private String name;
    private String country;

    public UserV1(String name, String country) {
        this.name = name;
        this.country = country;
    }

    // Getters and Setters
}

Controller for V1 (URI versioning shown here)

@RestController
@RequestMapping("/api/v1/users")
public class UserV1Controller {

    @GetMapping
    public UserV1 getUser() {
        return new UserV1("XYZ", "India");
    }
}

Adding a New Field: role (V2)

UserV2

public class UserV2 {
    private String name;
    private String country;
    private String role;

    public UserV2(String name, String country, String role) {
        this.name = name;
        this.country = country;
        this.role = role;
    }

    // Getters and Setters
}
  1. URI Path Versioning

🔧 Sample Endpoint:

GET /api/v2/users

Code Example:

@RestController
@RequestMapping("/api/v2/users")
public class UserV2Controller {

    @GetMapping
    public UserV2 getUser() {
        return new UserV2("XYZ", "India", "Java Developer");
    }
}

👍Pros:

  • Simple to implement

  • Version is visible and intuitive in the URL

  • Easy to test via browser or Postman

👎Cons:

  • Violates REST principles (version is not part of the resource)

  • Bloats endpoint URLs over time

  • Requires new controller classes per version

  1. Header Versioning

🔧 Sample Request:

GET /api/users
Header: X-API-VERSION: 2

Code Example:

@RestController
@RequestMapping("/api/users")
public class UserHeaderVersioningController {

    @GetMapping(headers = "X-API-VERSION=1")
    public UserV1 getUserV1() {
        return new UserV1("XYZ", "India");
    }

    @GetMapping(headers = "X-API-VERSION=2")
    public UserV2 getUserV2() {
        return new UserV2("XYZ", "India", "Java Developer");
    }
}

👍 Pros:

  • Keeps URLs clean

  • More REST-aligned than URI versioning

  • Easy to add new versions

👎 Cons:

  • Not directly accessible via browser

  • Requires custom headers

  • Not as user-friendly for external APIs

  1. Media-Type Versioning

🔧 Sample Request:

GET /api/users
Header: Accept: application/xyz.myapp.v1+json

Code Example:

@RestController
@RequestMapping("/api/users")
public class UserMediaTypeVersioningController {

    @GetMapping(produces = "application/xyz.app.v1+json")
    public UserV1 getUserV1() {
        return new UserV1("XYZ", "India");
    }

    @GetMapping(produces = "application/xyz.app.v2+json")
    public UserV2 getUserV2() {
        return new UserV2("XYZ", "India", "Java Developer");
    }
}

👍 Pros:

  • Fully REST-compliant

  • Allows precise content negotiation

  • Clean separation of versions

👎 Cons:

  • Complex for beginners

  • Harder to test manually

  • Can confuse third-party API consumers

When to use, what ?

ScenarioBest Versioning Strategy
External/Public APIURI versioning
Internal Controlled clientsHeader versioning
Strict REST Adherence NeededMedia-type versioning

🤝Let’s Connect!

Thanks for reading!

2
Subscribe to my newsletter

Read articles from Devanshu directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Devanshu
Devanshu