I implemented a solution that handles the PERFECTLY versioning issue with version control.
In general, there are three main approaches to managing vacation versions:
A statement-based path in which the client defines the version in the URL:
http://localhost:9001/api/v1/user http://localhost:9001/api/v2/user
The Content-Type header , in which the client defines the version in the Accept header:
http://localhost:9001/api/v1/user with Accept: application/vnd.app-1.0+json OR application/vnd.app-2.0+json
A custom header in which the client identifies the version in the custom header.
Problem with the first approach is that if you change the version, say, from v1 → v2, maybe you need to copy-paste resources v1 that haven’t been changed to path v2
Problem with the second approach, some tools like http://swagger.io/ cannot differ between operations with the same path, but with a different Content-Type (check issue https://github.com/ OAI / OpenAPI-Specification / issues / 146 )
Decision
Since I work a lot with recreational documentation tools, I prefer to use the first approach. My solution handles the problem using the first approach, so you don't need to copy the endpoint to the new version.
Let's say we have versions v1 and v2 for the user controller:
package com.mspapant.example.restVersion.controller; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; @Controller @Api(value = "user", description = "Operations about users") public class UserController { @ResponseBody @RequestMapping(method = RequestMethod.GET, value = "/api/v1/user") @ApiOperation(value = "Returns user", notes = "Returns the user", tags = {"GET", "User"}) public String getUserV1() { return "User V1"; } @ResponseBody @RequestMapping(method = RequestMethod.GET, value = "/api/v2/user") @ApiOperation(value = "Returns user", notes = "Returns the user", tags = {"GET", "User"}) public String getUserV2() { return "User V2"; } }
Demand is that if I request v1 for a user resource, I have to accept the answer "User V1" , otherwise if I request v2 , v3 , etc. I need to answer "Custom V2" .

To implement this in spring, we need to override the default behavior of RequestMappingHandlerMapping :
package com.mspapant.example.restVersion.conf.mapping; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; public class VersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping { @Value("${server.apiContext}") private String apiContext; @Value("${server.versionContext}") private String versionContext; @Override protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { HandlerMethod method = super.lookupHandlerMethod(lookupPath, request); if (method == null && lookupPath.contains(getApiAndVersionContext())) { String afterAPIURL = lookupPath.substring(lookupPath.indexOf(getApiAndVersionContext()) + getApiAndVersionContext().length()); String version = afterAPIURL.substring(0, afterAPIURL.indexOf("/")); String path = afterAPIURL.substring(version.length() + 1); int previousVersion = getPreviousVersion(version); if (previousVersion != 0) { lookupPath = getApiAndVersionContext() + previousVersion + "/" + path; final String lookupFinal = lookupPath; return lookupHandlerMethod(lookupPath, new HttpServletRequestWrapper(request) { @Override public String getRequestURI() { return lookupFinal; } @Override public String getServletPath() { return lookupFinal; }}); } } return method; } private String getApiAndVersionContext() { return "/" + apiContext + "/" + versionContext; } private int getPreviousVersion(final String version) { return new Integer(version) - 1 ; }
}
The implementation reads the version in the URL and requests from spring to resolve the URL. In case this URL does not exist (for example, the client requested v3 ), try with v2 and so on until we find the latest version for the resource.
To see the benefits of this implementation, let's say we have two resources: User and Company:
http://localhost:9001/api/v{version}/user http://localhost:9001/api/v{version}/company
Let's say we made changes to the "contract" of the company, which breaks the client. So, we implement http://localhost:9001/api/v2/company , and we ask the client to switch to v2 instead of v1.
So, new requests from the client:
http://localhost:9001/api/v2/user http://localhost:9001/api/v2/company
instead:
http://localhost:9001/api/v1/user http://localhost:9001/api/v1/company
the best part is that with this solution the client will get user information from v1 and company information from v2 without the need to create a new (same) user endpoint v2!
Leisure documentation As I said before, the reason I choose a URL-based approach is because some tools like swagger don't document endpoints with the same URL differently but with a different type of content. Both endpoints are displayed with this solution because they have different URLs:

Git
Implementation of the solution: https://github.com/mspapant/restVersioningExample/