Skip to content

Commit f895d76

Browse files
committed
Remove duplicate Content-Type header in error cases
Prior to this commit, the `DispatcherServlet` would try and reset the response buffer in case of errors, if the response is not committed already. This allows for more flexible error handling, even if the response was being handled already when it errored. Resetting the response buffer clears the body but leaves HTTP response headers intact. This is done on purpose as to not clear headers previously added by Servlet Filters. By leaving in place some headers like "Content-Type", this does not take into account the fact that the response body was cleared and that error handling will perform another round of content negotiation. While this isn't a problem for some Servlet containers which enforce a single "Content-Type" header value, this can cause multiple/duplicate values for some others. This commit ensures that the "Content-Type" response header is removed at the same time as we clear the "producible media types" attribute: another pass of content negotiation will be performed for error handling. Fixes gh-34366
1 parent 634d1dd commit f895d76

File tree

2 files changed

+24
-4
lines changed

2 files changed

+24
-4
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -48,6 +48,7 @@
4848
import org.springframework.core.io.ClassPathResource;
4949
import org.springframework.core.io.support.PropertiesLoaderUtils;
5050
import org.springframework.core.log.LogFormatUtils;
51+
import org.springframework.http.HttpHeaders;
5152
import org.springframework.http.HttpMethod;
5253
import org.springframework.http.MediaType;
5354
import org.springframework.http.server.RequestPath;
@@ -1341,9 +1342,10 @@ protected ModelAndView processHandlerException(HttpServletRequest request, HttpS
13411342

13421343
// Success and error responses may use different content types
13431344
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
1344-
// Reset the response body buffer if the response is not committed already,
1345-
// leaving the response headers in place.
1345+
// Reset the response content-type header and body buffer if the response is not committed already,
1346+
// leaving the other response headers in place.
13461347
try {
1348+
response.setHeader(HttpHeaders.CONTENT_TYPE, null);
13471349
response.resetBuffer();
13481350
}
13491351
catch (IllegalStateException illegalStateException) {

spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -924,6 +924,23 @@ void shouldAttemptToResetResponseBufferIfCommitted() throws Exception {
924924
assertThat(response.getHeader("Test-Header")).isEqualTo("spring");
925925
}
926926

927+
@Test
928+
void shouldResetContentTypeIfNotCommitted() throws Exception {
929+
StaticWebApplicationContext context = new StaticWebApplicationContext();
930+
context.setServletContext(getServletContext());
931+
context.registerSingleton("/error", ErrorController.class);
932+
DispatcherServlet servlet = new DispatcherServlet(context);
933+
servlet.init(servletConfig);
934+
935+
MockHttpServletRequest request = new MockHttpServletRequest(getServletContext(), "GET", "/error");
936+
MockHttpServletResponse response = new MockHttpServletResponse();
937+
assertThatThrownBy(() -> servlet.service(request, response)).isInstanceOf(ServletException.class)
938+
.hasCauseInstanceOf(IllegalArgumentException.class);
939+
assertThat(response.getContentAsByteArray()).isEmpty();
940+
assertThat(response.getStatus()).isEqualTo(400);
941+
assertThat(response.getHeaderNames()).doesNotContain(HttpHeaders.CONTENT_TYPE);
942+
}
943+
927944

928945
public static class ControllerFromParent implements Controller {
929946

@@ -976,6 +993,7 @@ private static class ErrorController implements Controller {
976993
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
977994
response.setStatus(400);
978995
response.setHeader("Test-Header", "spring");
996+
response.addHeader("Content-Type", "application/json");
979997
if (request.getAttribute("commit") != null) {
980998
response.flushBuffer();
981999
}

0 commit comments

Comments
 (0)