Implement Multi-Version REST APIs in SpringBoot with a Single Annotation
Learn how to implement multi-version REST APIs in SpringBoot 2.7.16 using a single custom annotation, custom RequestCondition, and HandlerMapping, enabling seamless version control, client compatibility, and flexible business scenarios with clear code examples and testing screenshots.
1. Introduction
Multiple versions of a Rest API are needed when the system upgrades functionality, when different client versions must be supported, or when diverse business scenarios require distinct API contracts.
2. Practical Example
2.1 Desired Effect
<code>@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("")
@ApiVersion("v1")
public Object v1() {
return "User v1";
}
@GetMapping("")
@ApiVersion("v2")
public Object v2() {
return "User v2";
}
}
</code>Without additional configuration, Spring cannot differentiate the two methods and will raise an error at startup.
2.2 Custom Annotation
<code>@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiVersion {
// specific version number
String value();
}
</code>The annotation marks a method with its API version.
2.3 Custom RequestCondition
<code>public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {
private final String apiVersion;
public ApiVersionCondition(String apiVersion) { this.apiVersion = apiVersion; }
@Override
public ApiVersionCondition combine(ApiVersionCondition other) { return this; }
@Override
public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
String requestedVersion = request.getHeader("X-API-Version");
if (apiVersion.equals(requestedVersion)) { return this; }
return null;
}
@Override
public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
return this.apiVersion.compareTo(other.apiVersion);
}
}
</code>This condition lets Spring select the method whose @ApiVersion matches the request header.
2.4 Custom HandlerMapping
<code>public class PackVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo mappingInfo = super.getMappingForMethod(method, handlerType);
if (mappingInfo != null) {
ApiVersion apiVersionAnnotation = method.getAnnotation(ApiVersion.class);
if (apiVersionAnnotation != null) {
String apiVersion = apiVersionAnnotation.value();
ApiVersionCondition apiVersionCondition = new ApiVersionCondition(apiVersion);
mappingInfo = mappingInfo.addCustomCondition(apiVersionCondition);
}
}
return mappingInfo;
}
@Override
protected boolean isHandler(Class<?> beanType) {
return super.isHandler(beanType);
}
}
</code>Register the custom mapping:
<code>@Component
public class PackWebMvcRegistrations implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new PackVersionRequestMappingHandlerMapping();
}
}
</code>2.5 Testing
When the request header does not contain X-API-Version , the endpoint returns 404.
Adding the header with value v1 or v2 invokes the corresponding method.
Based on the X-API-Version header value, the appropriate versioned API is called.
Conclusion
Multi-version Rest API interfaces are essential for handling system upgrades, client version differences, and varied business scenarios. By using a custom annotation and handler mapping, developers can implement version control easily, improving flexibility and scalability.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.