-
Notifications
You must be signed in to change notification settings - Fork 38.8k
Description
Spring Boot: 3.4.2
Spring Framework: 6.2.2
When setting ResponseEntity#contentType
- in the GET mapping return value of a REST controller,
- and the result of the exception handler, ...
... then the Content-Type header is duplicated on the response for a request that fails within StreamingResponseBody#writeTo:
HTTP/1.1 500 Server Error
Date: Tue, 04 Feb 2025 18:29:32 GMT
Vary: Accept-Encoding
Content-Type: application/json
Content-Type: application/json
Content-Length: 0
This might be specific to async requests, like when using StreamingResponseBody.
When debugging locally, I found that ...
Line 113 in 3c4d535
| private void writeHeaders() { |
ServletServerHttpResponse objects that hold the same HttpServletResponse. This might be expected, because different ServletServerHttpResponse objects can hold different header values. However, this leads to the content type being duplicated.
The two ServletServerHttpResponse are created at:
Line 72 in 3c4d535
ServerHttpResponse outputMessage = new ServletServerHttpResponse(response); Line 164 in 3c4d535
return new ServletServerHttpResponse(response); - through
spring-framework/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java
Line 1206 in 3c4d535
protected @Nullable ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
- through
Removing the content type at either of the places has a downside:
- GET mapping: There is no content-type set in the successful case
- Exception handler: There is no content-type set in the exception case for other APIs which are not async.
Is the duplication of content-type expected here?
Please let me know if more info is required. Thank you in advance, any help is appreciated!
Reproducer:
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
@RestController
@RequestMapping("stream")
@ControllerAdvice
public class StreamingRestApi {
@GetMapping(produces = "application/json")
public ResponseEntity<StreamingResponseBody> task() {
StreamingResponseBody streamingResponseBody = outputStream -> {
if (true) {
throw new RuntimeException();
}
};
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(streamingResponseBody);
}
@ExceptionHandler
public ResponseEntity<StreamingResponseBody> handleException(Exception exception) {
return ResponseEntity.internalServerError()
.contentType(MediaType.APPLICATION_JSON)
.build();
}
}