API version control is a key strategy to ensure the smooth evolution of the system. When the API changes, a reasonable version control mechanism can allow the old client to continue to work normally, while allowing the new client to use new features.
1. URL path version control
This is the most intuitive and widely used version control method, by directly including the version number in the URL path.
Implementation method
@RestController @RequestMapping("/api/v1/users") public class UserControllerV1 { @GetMapping("/{id}") public UserV1DTO getUser(@PathVariable Long id) { // Return the user information of v1 version return userService.getUserV1(id); } } @RestController @RequestMapping("/api/v2/users") public class UserControllerV2 { @GetMapping("/{id}") public UserV2DTO getUser(@PathVariable Long id) { // Returns the user information of v2 version, which may contain more fields return userService.getUserV2(id); } }
Pros and cons
advantage
- Simple and intuitive, clear client calls
- Completely isolate different versions of the API
- Easy API gateway routing and document management
shortcoming
- May cause code duplication
- Maintain multiple versions of the controller class
2. Request parameter version control
Keep the URL path unchanged by specifying the version number in the request parameter.
Implementation method
@RestController @RequestMapping("/api/users") public class UserController { @GetMapping("/{id}") public Object getUser(@PathVariable Long id, @RequestParam(defaultValue = "1") int version) { switch (version) { case 1: return userService.getUserV1(id); case 2: return userService.getUserV2(id); default: throw new IllegalArgumentException("Unsupported API version: " + version); } } }
Or use SpringMVC's conditional mapping:
@RestController @RequestMapping("/api/users") public class UserController { @GetMapping(value = "/{id}", params = "version=1") public UserV1DTO getUserV1(@PathVariable Long id) { return userService.getUserV1(id); } @GetMapping(value = "/{id}", params = "version=2") public UserV2DTO getUserV2(@PathVariable Long id) { return userService.getUserV2(id); } }
Pros and cons
advantage
- Maintain the semantics of URL resource positioning
- Relatively simple to implement
- The client can easily switch versions by querying parameters
shortcoming
- May be confused with business query parameters
- Inconvenient to cache (same URL different versions)
- Not as obvious as the URL path version
3. HTTP Header version control
This is a more RESTful way to specify the API version by customizing HTTP headers.
Implementation method
@RestController @RequestMapping("/api/users") public class UserController { @GetMapping(value = "/{id}", headers = "X-API-Version=1") public UserV1DTO getUserV1(@PathVariable Long id) { return userService.getUserV1(id); } @GetMapping(value = "/{id}", headers = "X-API-Version=2") public UserV2DTO getUserV2(@PathVariable Long id) { return userService.getUserV2(id); } }
Pros and cons
advantage
- Keep the URL clean and in line with RESTful concept
- Completely separate version information from business parameters
- Can carry more information about the version
shortcoming
- Not easy to test in the browser
- Higher requirements for API documentation
- The client needs special processing header information
4. Accept Header version control (media type version control)
Using the content negotiation mechanism of the HTTP protocol, specify the media type and its version through the Accept header.
Implementation method
@RestController @RequestMapping("/api/users") public class UserController { @GetMapping(value = "/{id}", produces = "application/-v1+json") public UserV1DTO getUserV1(@PathVariable Long id) { return userService.getUserV1(id); } @GetMapping(value = "/{id}", produces = "application/-v2+json") public UserV2DTO getUserV2(@PathVariable Long id) { return userService.getUserV2(id); } }
The Accept header needs to be set when requesting by the client:
Accept: application/-v2+json
Pros and cons
advantage
- Most compliant with HTTP specifications
- Using the existing mechanism of content negotiation
- Keep URLs clean and semantic
shortcoming
- The threshold for client usage is high
- Not intuitive, inconvenient to debug
- Custom MediaType parsing may be required
5. Custom annotation version control
More flexible version control with custom annotations and interceptors/filters.
Implementation method
First define the version annotation:
@Target({, }) @Retention() public @interface ApiVersion { int value() default 1; }
Create a version matching request mapping processor:
@Component public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping { @Override protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { ApiVersion apiVersion = (); return createCondition(apiVersion); } @Override protected RequestCondition<?> getCustomMethodCondition(Method method) { ApiVersion apiVersion = (); return createCondition(apiVersion); } private ApiVersionCondition createCondition(ApiVersion apiVersion) { return apiVersion == null ? new ApiVersionCondition(1) : new ApiVersionCondition(()); } } public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> { private final int apiVersion; public ApiVersionCondition(int apiVersion) { = apiVersion; } @Override public ApiVersionCondition combine(ApiVersionCondition other) { // Use the highest version return new ApiVersionCondition((, )); } @Override public ApiVersionCondition getMatchingCondition(HttpServletRequest request) { String version = ("X-API-Version"); if (version == null) { version = ("version"); } int requestedVersion = version == null ? 1 : (version); return requestedVersion >= apiVersion ? this : null; } @Override public int compareTo(ApiVersionCondition other, HttpServletRequest request) { // Preferentially match higher versions return - ; } }
Configure WebMvc to use a custom mapping processor:
@Configuration public class WebConfig implements WebMvcConfigurer { @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping() { return new ApiVersionRequestMappingHandlerMapping(); } }
Use custom annotations:
@RestController @RequestMapping("/api/users") public class UserController { @ApiVersion(1) @GetMapping("/{id}") public UserV1DTO getUserV1(@PathVariable Long id) { return userService.getUserV1(id); } @ApiVersion(2) @GetMapping("/{id}") public UserV2DTO getUserV2(@PathVariable Long id) { return userService.getUserV2(id); } }
Pros and cons
advantage
- Highly flexible and customizable
- Can combine multiple version control strategies
- The code organization is clearer
shortcoming
- More complex implementation
- Need to customize Spring components
6. Interface-oriented API version control
The core idea is to provide different version implementation classes with the same interface.
Implementation method
First define the API interface:
public interface UserApi { Object getUser(Long id); } @Service @Primary public class UserApiV2Impl implements UserApi { // The latest version is implemented @Override public UserV2DTO getUser(Long id) { // Return V2 version data return new UserV2DTO(); } } @Service @Qualifier("v1") public class UserApiV1Impl implements UserApi { // Old version implementation @Override public UserV1DTO getUser(Long id) { // Return V1 version data return new UserV1DTO(); } }
The controller layer dynamically selects implementation based on version:
@RestController @RequestMapping("/api/users") public class UserController { private final Map<Integer, UserApi> apiVersions; // Collect all implementations through construct injection public UserController(List<UserApi> apis) { // Simplify the example, you should actually mark the version of each implementation in some way = ( 1, ().filter(api -> api instanceof UserApiV1Impl).findFirst().orElseThrow(), 2, ().filter(api -> api instanceof UserApiV2Impl).findFirst().orElseThrow() ); } @GetMapping("/{id}") public Object getUser(@PathVariable Long id, @RequestParam(defaultValue = "2") int version) { UserApi api = (version, (2)); // Use the latest version by default return (id); } }
You can implement a version delegator yourself to simplify version selection:
// Custom API version delegatorpublic class ApiVersionDelegator<T> { private final Class<T> apiInterface; private final Map<String, T> versionedImpls = new HashMap<>(); private final Function<HttpServletRequest, String> versionExtractor; private final String defaultVersion; public ApiVersionDelegator(Class<T> apiInterface, Function<HttpServletRequest, String> versionExtractor, String defaultVersion, ApplicationContext context) { = apiInterface; = versionExtractor; = defaultVersion; // Find all beans that implement this interface from Spring context Map<String, T> impls = (apiInterface); for (<String, T> entry : ()) { ApiVersion apiVersion = ().getClass().getAnnotation(); if (apiVersion != null) { ((()), ()); } } } public T getApi(HttpServletRequest request) { String version = (request); return (version, (defaultVersion)); } // Builder pattern simplifies the creation process public static <T> Builder<T> builder() { return new Builder<>(); } public static class Builder<T> { private Class<T> apiInterface; private Function<HttpServletRequest, String> versionExtractor; private String defaultVersion; private ApplicationContext applicationContext; public Builder<T> apiInterface(Class<T> apiInterface) { = apiInterface; return this; } public Builder<T> versionExtractor(Function<HttpServletRequest, String> versionExtractor) { = versionExtractor; return this; } public Builder<T> defaultVersion(String defaultVersion) { = defaultVersion; return this; } public Builder<T> applicationContext(ApplicationContext applicationContext) { = applicationContext; return this; } public ApiVersionDelegator<T> build() { return new ApiVersionDelegator<>(apiInterface, versionExtractor, defaultVersion, applicationContext); } } }
Configuring and using the delegator:
@Configuration public class ApiConfiguration { @Bean public ApiVersionDelegator<UserApi> userApiDelegator(ApplicationContext context) { return ApiVersionDelegator.<UserApi>builder() .apiInterface() .versionExtractor(request -> { String version = ("X-API-Version"); return version == null ? "2" : version; }) .defaultVersion("2") .applicationContext(context) .build(); } } @RestController @RequestMapping("/api/users") public class UserController { private final ApiVersionDelegator<UserApi> apiDelegator; public UserController(ApiVersionDelegator<UserApi> apiDelegator) { = apiDelegator; } @GetMapping("/{id}") public Object getUser(@PathVariable Long id, HttpServletRequest request) { UserApi api = (request); return (id); } }
Pros and cons
advantage
- Realize the separation of concerns
- Follow the principle of opening and closing, the new version only needs to add a new implementation
- Decoupling of business logic and version control
shortcoming
- A good interface hierarchy is required
- Additional adaptation layers may be required to deal with return type differences
- Initial setup is more complicated
7. Summary
The above 6 API version control methods have their own advantages and disadvantages. The following factors should be considered when choosing.
- Project size and team situation: Small projects can choose simple URL path versioning, while large projects can consider custom annotations or interface-oriented approaches
- Client Type: Browser-oriented APIs may be more suitable for URL paths or query parameter versioning, while APIs for mobile applications or other services may consider HTTP header or media type versioning
- Version evolution strategy: Whether backward compatibility is required, how often the version update is
- API Gateway and Documentation: Consider whether the version control method facilitates API gateway routing and document generation
Finally, version control is just a means, not an end. The key is to build an evolving API architecture so that the system can continuously meet changes in business needs. Choosing the appropriate version control strategy can achieve smooth evolution of the API while ensuring system stability.
The above is the detailed content of the summary of the 6 API version control strategies in SpringBoot. For more information about SpringBoot API version control, please follow my other related articles!