Mastering API Versioning in Spring Framework 7.0: Strategies and Code Samples
Spring Framework 7.0 introduces native API versioning support, enabling developers to manage multiple API versions through URI, request header, query parameter, and content negotiation strategies, with detailed implementation examples, customizable resolvers, and client-side usage via WebClient, ensuring backward compatibility and flexible version control.
API versioning is a key practice in modern web development that lets developers evolve APIs without breaking existing clients.
Importance of API Versioning
Versioning allows new features, bug fixes, or data‑structure changes while preserving backward compatibility. Common strategies include:
URI versioning : embed the version in the URL path, e.g.
/api/v1/users.
Request‑header versioning : specify the version in a header such as
Accept: application/vnd.company.app-v1+json.
Query‑parameter versioning : pass the version as a query parameter, e.g.
/api/users?version=1.
Content‑negotiation versioning : determine the version from the media type in the
Acceptheader.
Spring 7.0 API Versioning
Spring Framework 7.0 adds native support for API versioning on the server side. By specifying a version range in the
@RequestMappingannotation, requests are routed to different controller methods, simplifying multi‑version API management.
Implementation Approach
Developers can define the version range in
@RequestMapping. Spring resolves the version from the request using one of several sources:
Request URL path : e.g.
/api/v1/usersor
/api/v2/users.
Request‑header value : e.g.
Accept: application/vnd.company.app-v1+json.
Other custom sources : developers can configure additional resolvers.
Example controller with two versioned endpoints:
<code>import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@RequestMapping(value = "/api/users", version = "1.0")
public ResponseEntity<List<UserV1>> getUsersV1() {
// version 1.0 implementation
List<UserV1> users = fetchUsersV1();
return ResponseEntity.ok(users);
}
@RequestMapping(value = "/api/users", version = "2.0")
public ResponseEntity<List<UserV2>> getUsersV2() {
// version 2.0 implementation
List<UserV2> users = fetchUsersV2();
return ResponseEntity.ok(users);
}
}
</code>In this example the
/api/usersendpoint dispatches to different methods based on the requested version (1.0 or 2.0), which can be supplied via URL path or request header.
Client‑side API Versioning
Spring also enhances client capabilities. Using
WebClientor other tools, developers can set the desired API version in the request, ensuring the client talks to the correct version when multiple versions are deployed.
Client Example
Using
WebClientto request version 1.0:
<code>import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
public class UserClient {
private final WebClient webClient;
public UserClient(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("http://example.com").build();
}
public Mono<List> getUsersV1() {
return webClient.get()
.uri("/api/users")
.accept(MediaType.valueOf("application/vnd.company.app-v1+json"))
.retrieve()
.bodyToMono(List.class);
}
}
</code>The client sets the
Acceptheader to
application/vnd.company.app-v1+json, ensuring it receives the version 1.0 API.
Source Code Analysis of API Versioning
Spring’s implementation relies on several core classes.
ApiVersionResolver Interface
The functional interface defines how to extract a version from a request:
<code>@FunctionalInterface
public interface ApiVersionResolver {
/**
* Resolve the version from the given exchange.
* @param exchange the current exchange
* @return the version value, or null if not found
*/
@Nullable
String resolveVersion(ServerWebExchange exchange);
}
</code>PathApiVersionResolver Implementation
This class extracts the version from a specific segment of the URL path:
<code>public class PathApiVersionResolver implements ApiVersionResolver {
private final int pathSegmentIndex;
public PathApiVersionResolver(int pathSegmentIndex) {
Assert.isTrue(pathSegmentIndex >= 0, "'pathSegmentIndex' must be >= 0");
this.pathSegmentIndex = pathSegmentIndex;
}
@Override
@Nullable
public String resolveVersion(ServerWebExchange exchange) {
int i = 0;
for (PathContainer.Element e : exchange.getRequest().getPath().pathWithinApplication().elements()) {
if (e instanceof PathContainer.PathSegment && i++ == this.pathSegmentIndex) {
return e.value();
}
}
return null;
}
}
</code>For a path like
/api/v1/userswith
pathSegmentIndex = 1, the resolver returns
v1.
DefaultApiVersionStrategy Class
The default strategy handles parsing, comparing, and validating versions:
<code>public class DefaultApiVersionStrategy implements ApiVersionStrategy {
@Override
@Nullable
public Comparable<?> parseVersion(ServerWebExchange exchange) {
// implementation omitted for brevity
}
@Override
@Nullable
public Comparable<?> findDefaultVersion() {
return this.defaultVersion;
}
@Override
public boolean isVersionRequired() {
return this.versionRequired;
}
@Override
public void validateVersion(@Nullable Comparable<?> requestVersion, ServerWebExchange exchange) throws MissingApiVersionException, InvalidApiVersionException {
// validation logic
}
}
</code>Version Request Condition
The
VersionRequestConditionclass integrates with Spring’s request‑mapping mechanism to route requests based on the resolved version:
<code>public class VersionRequestCondition implements RequestCondition<VersionRequestCondition> {
@Override
@Nullable
public VersionRequestCondition getMatchingCondition(ServerWebExchange exchange) {
Comparable<?> requestVersion = this.versionStrategy.parseVersion(exchange);
return this;
}
}
</code>Conclusion
Spring Framework 7.0’s API versioning provides a flexible, extensible mechanism that lets developers manage API versions in a standardized way. Core components such as
ApiVersionResolver,
ApiVersionStrategy, and
VersionRequestConditionwork together to parse, validate, and route versioned requests, supporting common strategies while allowing custom extensions for specific business needs.
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.