Spring Boot Tutorial
API Versioning
Once other teams, mobile apps or customers depend on your API, you cannot change it freely. Renaming a field or changing a response shape breaks clients that have not updated yet — and mobile apps may stay on old versions for months. API versioning lets you introduce breaking changes as a new version while old clients keep working.
Spring Framework 7 adds first-class API versioning to Spring MVC and WebFlux, and Spring Boot 4 configures it with spring.mvc.apiversion.* properties. This lesson compares versioning strategies, shows the new version attribute on mappings, and covers defaults, supported versions, deprecation and how to avoid versioning at all when possible.
Avoid Breaking Changes First
Many changes need no new version: adding optional request fields, adding response fields (clients should ignore unknown fields), adding new endpoints. Breaking changes are removing or renaming fields, changing types or meanings, and making optional input required. Evolve additively where you can, and version only when you must.
Versioning Strategies
There are four common places to put the version:
- Path segment —
/api/v2/orders. Most visible and cache-friendly; widely used for public APIs. - Request header —
X-API-Version: 2. Keeps URLs stable; common for internal APIs. - Query parameter —
/api/orders?version=2. Easy to try in a browser. - Media type —
Accept: application/vnd.webnest.v2+json. Most "RESTful", least convenient.
Spring Framework 7 Versioned Mappings
Mapping annotations now have a version attribute: @GetMapping(path = "/orders/{id}", version = "2"). A version like "1.1+" is a baseline: it matches 1.1 and any later version unless a more specific mapping exists, so you only write new methods for endpoints that actually changed. Spring resolves the version from the request (header, query parameter, path segment or media type parameter), parses it, validates it against the supported versions, and picks the best matching handler.
Configuration
In Spring Boot 4, set the strategy with properties such as spring.mvc.apiversion.use.header=X-API-Version and a default with spring.mvc.apiversion.default. For path-segment versioning or combined strategies, implement WebMvcConfigurer.configureApiVersioning(ApiVersionConfigurer). Requests with an unsupported version are rejected with 400.
Deprecating Old Versions
Announce retirement dates. Spring can add standard Deprecation, Sunset and Link response headers for deprecated versions through an ApiVersionDeprecationHandler, so client developers see warnings in their logs long before a version is removed. Monitor usage per version and remove it only when traffic has gone.
Examples
Header-based versioning configured with properties
# application.yml
spring:
mvc:
apiversion:
use:
header: X-API-Version
default: 1.0
public record OrderV1(Long id, String customerName, double total) {}
public record OrderV2(Long id, CustomerRef customer, Money total, String status) {}
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@GetMapping(path = "/{id}", version = "1.0")
public OrderV1 getV1(@PathVariable Long id) {
return new OrderV1(id, "Asha Rao", 1299.0);
}
@GetMapping(path = "/{id}", version = "2.0")
public OrderV2 getV2(@PathVariable Long id) {
return new OrderV2(id, new CustomerRef(7L, "Asha Rao"), new Money(new BigDecimal("1299.00"), "INR"), "PAID");
}
// Unchanged endpoint: one method serves 1.0 and every later version
@GetMapping(version = "1.0+")
public List<Long> list() {
return List.of(41L, 42L);
}
}
GET /api/orders/42 -> {"id":42,"customerName":"Asha Rao","total":1299.0} (default 1.0)
GET /api/orders/42 X-API-Version: 2.0 -> {"id":42,"customer":{"id":7,"name":"Asha Rao"},"total":{"amount":1299.00,"currency":"INR"},"status":"PAID"}
GET /api/orders X-API-Version: 2.0 -> [41,42] (baseline 1.0+ mapping)
GET /api/orders/42 X-API-Version: 9.0 -> 400 Bad Request (unsupported version)
Path-segment versioning configured in code
@Configuration
public class ApiVersionConfig implements WebMvcConfigurer {
@Override
public void configureApiVersioning(ApiVersionConfigurer configurer) {
configurer
.usePathSegment(1) // /api/{version}/... -> segment index 1
.addSupportedVersions("1", "2")
.setDefaultVersion("1");
}
}
@RestController
@RequestMapping("/api/{version}/courses")
public class CourseController {
@GetMapping(version = "1")
public List<String> v1() {
return List.of("Java Core", "Spring Boot");
}
@GetMapping(version = "2")
public List<Map<String, Object>> v2() {
return List.of(Map.of("title", "Java Core", "lessons", 60),
Map.of("title", "Spring Boot", "lessons", 100));
}
}
GET /api/v1/courses -> ["Java Core","Spring Boot"]
GET /api/v2/courses -> [{"title":"Java Core","lessons":60},{"title":"Spring Boot","lessons":100}]
Calling a versioned API from RestClient
RestClient client = RestClient.builder()
.baseUrl("https://api.webnest.in")
.apiVersionInserter(ApiVersionInserter.useHeader("X-API-Version"))
.build();
OrderV2 order = client.get()
.uri("/api/orders/{id}", 42)
.apiVersion("2.0")
.retrieve()
.body(OrderV2.class);
GET https://api.webnest.in/api/orders/42
X-API-Version: 2.0
Common Mistakes
- Creating a new version for additive, non-breaking changes, multiplying code to maintain.
- Copying every controller for v2 instead of using baseline versions ("1.0+") for unchanged endpoints.
- Removing old versions without announcing deprecation or checking whether clients still use them.
- Mixing several versioning strategies inconsistently across services in the same organisation.
- Versioning internal service-layer classes as well — keep versions at the API edge and map to one domain model.
Key Points to Remember
- Prefer additive, backward-compatible changes; version only for breaking changes.
- Spring Framework 7 adds a version attribute to request mappings; "1.1+" means that version and later.
- Spring Boot 4 configures versioning with spring.mvc.apiversion.* (header, default version, and more).
- Use configureApiVersioning for path-segment or combined strategies and supported versions.
- Signal deprecation with Deprecation/Sunset headers and monitor usage before removal.
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Use your local JDK or project IDE for these examples. Codelab currently runs Python and HTML/CSS/JavaScript; framework examples may need project dependencies.