Learnitweb

Application versioning in a Spring Boot API

Implementing application versioning in a Spring Boot REST API is crucial for supporting backward compatibility when your API evolves. There are several strategies to version a Spring Boot API, and the method you choose depends on your design goals and client needs.

Common Strategies for API Versioning

1. URI Path Versioning (Recommended)

Most widely used method: Version is part of the URL path like /v1/..., /v2/...

Example:

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

    @GetMapping
    public List<String> getUsers() {
        return List.of("User1", "User2");
    }
}

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

    @GetMapping
    public List<String> getUsers() {
        return List.of("User1", "User2", "User3");
    }
}

Pros:

  • Easy to test and debug
  • Cache-friendly
  • Follows REST best practices

Cons:

  • Duplicates endpoints if many versions exist

2. Request Parameter Versioning

Use a query parameter to specify version:
Example: /api/users?version=1

Implementation:

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

    @GetMapping(params = "version=1")
    public String getV1() {
        return "User API V1";
    }

    @GetMapping(params = "version=2")
    public String getV2() {
        return "User API V2";
    }
}

Pros:

  • Single URL, cleaner routing logic
  • Allows default versioning

Cons:

  • Not very RESTful
  • More logic in controllers to handle parameters

3. Header Versioning

Clients pass version via custom headers like:
X-API-VERSION: 1

Implementation:

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

    @GetMapping(headers = "X-API-VERSION=1")
    public String getV1() {
        return "User API V1";
    }

    @GetMapping(headers = "X-API-VERSION=2")
    public String getV2() {
        return "User API V2";
    }
}

Pros:

  • Keeps URLs clean
  • Clients can dynamically switch versions via headers

Cons:

  • Not easily cacheable
  • More complex for testing/debugging

4. Content Negotiation (Media Type Versioning)

Uses the Accept header to specify version:

Accept Header Example:
Accept: application/vnd.myapp.v1+json

Implementation:

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

    @GetMapping(produces = "application/vnd.myapp.v1+json")
    public String getV1() {
        return "User API V1";
    }

    @GetMapping(produces = "application/vnd.myapp.v2+json")
    public String getV2() {
        return "User API V2";
    }
}

Pros:

  • Clean URL
  • Flexible for complex content negotiation

Cons:

  • Difficult for clients to set headers correctly
  • Hard to test via browser or tools like Postman

Versioning Best Practices

  1. Start with URI versioning for simplicity and clarity.
  2. Keep each version isolated with its own controller or package.
  3. Deprecate old versions gracefully and document timelines.
  4. Provide changelogs and API documentation for each version using tools like Swagger (SpringDoc).
  5. If you use Swagger/OpenAPI, configure versioned docs like /v1/api-docs.

Example Project Structure (URI Versioning)

src
└── main
    └── java
        └── com.example.demo
            ├── controller
            │   ├── v1
            │   │   └── UserControllerV1.java
            │   └── v2
            │       └── UserControllerV2.java