While dealing with Spring MVC, I had the wonderful opportunity of dealing with servlet filters. One thing we needed to accomplish was adding a header after the controller does the processing. It can be done in the controller method, right? But we needed to apply this modification to every incoming request. So, let's start by implementing a new interceptor:

public class DummyInterceptor extends HandlerInterceptorAdapter {

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        response.addHeader("dummy-header, "dummy-value");
    }
}

This should do the trick, we thought. Well, no. Following filter does not work also:

@Component
public class DummyFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            filterChain.doFilter(request, response);
        } finally {
            response.addHeader("dummy-header, "dummy-value");
        }
    }
}

The reason is as Spring document states:

Note that the postHandle method of HandlerInterceptor is not always ideally suited for use with @ResponseBody and ResponseEntity methods. In such cases an HttpMessageConverter writes to and commits the response before postHandle is called which makes it impossible to change the response, for example to add a header. Instead an application can implement ResponseBodyAdvice and either declare it as an @ControllerAdvice bean or configure it directly on RequestMappingHandlerAdapter.

The response is committed before we can put hands on it. We couldn't manage to succeed with ControllerAdvice and ResponseBodyAdvice. Our case was a little specific, we only needed to modify the response headers when a specific status code is returned from the controller. ResponseBodyAdvice does not provide the processed status code. The simple header modification with ResponseBodyAdvice is as follows:

@ControllerAdvice
public class HeaderModifierAdvice implements ResponseBodyAdvice<Object> {

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        response.getHeaders().add("dummy-header","dummy-value");
        return body;
    }
}

So, after some time with SO, the suggested code was successful for us. It simply wraps the HttpServletResponse object to add headers when status code is set. To modify the response headers when a specific status code is returned is as follows:

@Component
public class DummyFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper(response) {
            @Override
            public void setStatus(int sc) {
                super.setStatus(sc);
                handleStatus(sc);
            }
            @Override
            @SuppressWarnings("deprecation")
            public void setStatus(int sc, String sm) {
                super.setStatus(sc, sm);
                handleStatus(sc);
            }
            @Override
            public void sendError(int sc, String msg) throws IOException {
                super.sendError(sc, msg);
                handleStatus(sc);
            }
            @Override
            public void sendError(int sc) throws IOException {
                super.sendError(sc);
                handleStatus(sc);
            }
            private void handleStatus(int code) {
                if(code == 404)
                    addHeader("dummy-header, "dummy-value");
            }
        };
        filterChain.doFilter(request, wrapper);
    }
}

The number of headers can be expanded. Not all the overrides are necessary but they are included for edge cases.