Search code examples
springspring-bootspring-restapi-versioning

How to by default execute the latest version of endpoint in Spring Boot REST?


I'm developing Spring Boot v2.2.2.RELEASE and API versioning using Custom Header. By default I want latest version of endpoint to be executed.

Here in below code its X-API-VERSION=2. Before executing the endpoint I need to check mapping and then set the latest version in for the Custom Header and then execute it.

@GetMapping(value = "/student/header", headers = {"X-API-VERSION=1"})
public StudentV1 headerV1() {
    return serviceImpl.headerV1();
}

@GetMapping(value = "/student/header", headers = {"X-API-VERSION=2"})
public StudentV1 headerV2() {
    return serviceImpl.headerV2();
}

Solution

  • If I got your question right, you want that the request sent without X-API-VERSION header will automatically be routed to headerV2() method

    This can be done:

    Conceptually you'll need a Web Filter that gets executed before the Spring MVC Dispatch Servlet and you'll have to check whether the request contains a header and if it doesn't add a header of the version that you think should be the latest one (from configuration or something).

    One caveat here is that the HttpServletRequest doesn't allow adding Headers so you'll have to create a HttpServletRequestWrapper and implement the logic of header addition there.

    Here is an example (I've borrowed the implementation from this question):

    // The web filter 
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.io.IOException;
    
    @Component
    @Order(1)
    public class LatestVersionFilter implements Filter {
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest req = (HttpServletRequest) servletRequest;
            if(req.getHeader("X-API-VERSION")== null) {
                HeaderMapRequestWrapper wrappedRequest = new HeaderMapRequestWrapper(req);
                wrappedRequest.addHeader("X-API-VERSION", resolveLastVersion());
                filterChain.doFilter(wrappedRequest, servletResponse);
            } else {
                filterChain.doFilter(servletRequest, servletResponse);
            }
        }
    
        private String resolveLastVersion() {
            // read from configuration or something
            return "2";
        }
    }
    

    Note that the request is wrapped to add the header as I've explained:

    class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
            /**
             * construct a wrapper for this request
             *
             * @param request
             */
            public HeaderMapRequestWrapper(HttpServletRequest request) {
                super(request);
            }
    
            private Map<String, String> headerMap = new HashMap<String, String>();
    
            /**
             * add a header with given name and value
             *
             * @param name
             * @param value
             */
            public void addHeader(String name, String value) {
                headerMap.put(name, value);
            }
    
            @Override
            public String getHeader(String name) {
                String headerValue = super.getHeader(name);
                if (headerMap.containsKey(name)) {
                    headerValue = headerMap.get(name);
                }
                return headerValue;
            }
    
            /**
             * get the Header names
             */
            @Override
            public Enumeration<String> getHeaderNames() {
                List<String> names = Collections.list(super.getHeaderNames());
                for (String name : headerMap.keySet()) {
                    names.add(name);
                }
                return Collections.enumeration(names);
            }
    
            @Override
            public Enumeration<String> getHeaders(String name) {
                List<String> values = Collections.list(super.getHeaders(name));
                if (headerMap.containsKey(name)) {
                    values.add(headerMap.get(name));
                }
                return Collections.enumeration(values);
            }
    
        }
    

    With this implementation (make sure you put the servlet in the package next to controller or something so that the spring boot will recognize it as a component) you'll see:

    1. GET with header X-API-VERSION=1 will be routed to headerV1()
    2. GET with header X-API-VERSION=2 will be routed to headerV2()
    3. GET without X-API-VERSION header will be routed to headerV2()