Affects: Spring Framework 5.2.5.RELEASE


When throwing a derived exception like:

    public class NotModifiedStatusException extends ResponseStatusException {

        private static final long serialVersionUID = -4380761922076567306L;

        private String etag;

        public NotModifiedStatusException(String etag) {
            super(HttpStatus.NOT_MODIFIED);
            this.etag = etag;
        }

        @Override
        public Map<String, String> getHeaders() {
            return getResponseHeaders().toSingleValueMap();
        }

        @Override
        public HttpHeaders getResponseHeaders() {
            HttpHeaders headers = new HttpHeaders();
            headers.setETag(etag);
            return headers;
        }

    }

the headers are completely ignored:

17:56:01,681 [http-nio-127.0.0.1-8081-exec-5] DEBUG o.s.w.s.m.a.ResponseStatusExceptionResolver: Resolved [...NotModifiedStatusException: 304 NOT_MODIFIED]
17:56:01,681 [http-nio-127.0.0.1-8081-exec-5] TRACE o.s.web.servlet.DispatcherServlet: No view rendering, null ModelAndView returned.
17:56:01,681 [http-nio-127.0.0.1-8081-exec-5] DEBUG o.s.web.servlet.DispatcherServlet: Completed 304 NOT_MODIFIED, headers={}

It happily ignores the headers: https://github.com/spring-projects/spring-framework/blob/8d31dcaa297c3e9001a6e8470b400641ae1c49d0/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java#L128-L133

My naive solution seems to work:

@Component
public class HeaderAwareResponseStatusExceptionResolver extends ResponseStatusExceptionResolver {

    @Override
    public int getOrder() {
        return super.getOrder() + 1;
    }

    @Override
    protected ModelAndView resolveResponseStatusException(ResponseStatusException ex,
            HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        int statusCode = ex.getStatus().value();
        String reason = ex.getReason();
        HttpHeaders headers = ex.getResponseHeaders();

        headers.forEach((name, values) -> values.forEach(value -> response.addHeader(name, value)));

        if (!StringUtils.hasLength(reason)) {
            response.sendError(statusCode);
        } else {
            response.sendError(statusCode, reason);
        }
        return new ModelAndView();
    }

}

Comment From: rstoyanchev

Support for headers was added recently in #24261 but only on the WebFlux side it seems.

Comment From: michael-o

Correct, this is my observation. I assume this is simply an oversight.

Comment From: michael-o

The provided solution is in consistent with the Flux version:

https://github.com/spring-projects/spring-framework/blob/ea6d2ea1ce6d42c67d8ca5e34d24bbca625c9ced/spring-web/src/main/java/org/springframework/web/server/handler/ResponseStatusExceptionHandler.java#L94-L97

It does not contain a null check.

Comment From: rstoyanchev

Yes it is a little more defensive. Spring MVC has been around longer and is used more widely. Do you foresee an issue?

Comment From: michael-o

Not right now, but this deserves to be documented.

Comment From: rstoyanchev

I've updated it to match WebMvc.