Summary
- When using
@ExceptionHandler
(or@ControllerAdvice
), exceptions thrown while writing to aStreamingResponseBody
are not handled correctly (or at least as I expect them to be...) when the request mapping returnsResponseEntity<StreamingResponseBody>
- Exception handling does work as expected when the return type is just
StreamingResponseBody
- It's possible my understanding of how these should work is incorrect, however I haven't seen any evidence yet to point to this
Description
- If a runtime exception occurs while writing to the
StreamingResponseBody
wrapped in aResponseEntity
the endpoint does invoke the@ExceptionHandler
but returns a500
status with no response body no matter what - If the request mapping returns a
StreamingResponseBody
directly, error handling does work as expected, and the response code and response body are correct - While writing tests for this, I can't get the tests to return anything other than 200 status. While running tests, I also don't see the exception handlers getting invoked. I'm not sure if this is another bug or if my test setup is incorrect. If you hit the endpoints directly in the browser or via CURL, you do see the 500 status with no body. This is my first time using
StreamingResponseBody
so I apologize if I'm just doing things incorrectly - Sample project to reproduce: https://github.com/austinarbor/spring-boot-streaming-response-issue
Misc
- Spring Boot: 2.3.1 / 2.3.2
- Spring Framework: 5.2.8
- Java Version: 11
- StackOverflow Question
Comment From: vforchi
I have a similar issue: the application works as expected, no matter if I wrap the StreamingResponseBody
in a ResponseEntity
or not, but in the tests I always get a 200 and an empty body.
I tried with @WebMvcTest
+mockMvc
and with @SpringBootTest
+TestRestTemplate
: same behaviour.
Comment From: rstoyanchev
The methods that return ResponseEntity
set the content type to "text/csv" so the exception handler later fails to write the ErrorDetails with that content type. I see:
2020-12-01 21:18:59.916 INFO 331037 --- [ XNIO-1 task-1] dev.aga.controller.SomeController : Inside handleIllegalArgumentException
2020-12-01 21:19:00.788 WARN 331037 --- [ XNIO-1 task-1] .m.m.a.ExceptionHandlerExceptionResolver : Failure in @ExceptionHandler dev.aga.controller.SomeController#handleIllegalArgumentException(IllegalArgumentException, HttpServletRequest, HttpServletResponse)
org.springframework.http.converter.HttpMessageNotWritableException: No converter for [class dev.aga.model.ErrorDetails] with preset Content-Type 'text/csv'
You'll need to also set the content type on the ResponseEntity
of the error handler method. However I found out that doesn't work because the ServletServerHttpResponse
wrapper does not propagate header changes to the underlying response until it writes the body, and yet it looks up values in it. We'll need to fix that.
In the mean time you can work around this by setting the content type directly on the response:
@ExceptionHandler(IllegalArgumentException.class)
ResponseEntity<ErrorDetails> handleIllegalArgumentException(IllegalArgumentException e, HttpServletRequest request, HttpServletResponse response) {
log.info("Inside handleIllegalArgumentException");
var errorDetails = new ErrorDetails(request.getRequestURI(), e.getMessage());
response.setContentType("application/json");
return ResponseEntity.status(HttpStatus.BAD_GATEWAY)
.contentType(MediaType.APPLICATION_JSON)
.body(errorDetails);
}
Comment From: rstoyanchev
As for the tests, MockMvc doesn't perform async dispatches, so all you see is the initial handling up until the point the async result is produced. You can perform the async dispatches manually. This is described in the docs. However I would also recommend using the WebTestClient support with MockMvc that's new 5.3
Comment From: austinarbor
Hi @rstoyanchev thanks for you response. Based on your info, I think these are the changes required to make everything work: https://github.com/austinarbor/spring-boot-streaming-response-issue/pull/1/files
Comment From: rstoyanchev
@austinarbor yes that would be the workaround for the @ExceptionHandler
method. Also the test updates look about right from a quick look.