The Actuator's endpoints are registered with Spring MVC through this handler method:
https://github.com/spring-projects/spring-boot/blob/8f898ba74db75679926f249c824d1a8829e6da36/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/endpoint/web/servlet/AbstractWebMvcEndpointHandlerMapping.java#L419-L422
Returning Object means that the method signature provides minimal type information to MVC about the response. This minimal type information isn't a problem when a Mono is returned directly as ResponseBodyEmitterReturnValueHandler can still identify that a Mono is being returned and handle the return value. When the further indirection of returning a ResponseEntity<Mono> is introduced, ResponseBodyEmitterReturnValueHandler fails to identify that a Mono is being returned and does not handle the return value.
This behaviour can be illustrated without Actuator with the following handler methods on a @RestController:
@GetMapping("mono")
public Mono<String> mono() {
return Mono.just("a");
}
@GetMapping("monoAsObject")
public Object monoAsObject() {
return Mono.just("a");
}
@GetMapping("responseEntity")
public ResponseEntity<Mono<String>> responseEntity() {
return ResponseEntity.ok().body(Mono.just("a"));
}
@GetMapping("responseEntityAsObject")
public Object responseEntityAsObject() {
return ResponseEntity.ok().body(Mono.just("a"));
}
The first three are fine, each producing a response similar to the following:
HTTP/1.1 200
Connection: keep-alive
Content-Length: 1
Content-Type: text/plain;charset=UTF-8
Date: Thu, 10 Mar 2022 11:13:55 GMT
Keep-Alive: timeout=60
a
The fourth doesn't work. No async handling is performed and instead the Mono itself is serialised to JSON:
$ http :8080/responseEntityAsObject
HTTP/1.1 200
Connection: keep-alive
Content-Type: application/json
Date: Thu, 10 Mar 2022 11:14:08 GMT
Keep-Alive: timeout=60
Transfer-Encoding: chunked
{
"scanAvailable": true
}
The problem doesn't not occur with WebFlux, either with Actuator or directly with a @RestController with the four handler methods above. As such, this may be a Spring MVC bug or limitation.
When ResponseBodyEmitterReturnValueHandler.supportsReturnType(MethodParameter) is called, a HandlerMethod$ReturnValueMethodParameter is passed in. Its returnValue contains the ResponseEntity with the Mono body. This means that, in this specific case at least, there is sufficient information available to determine that it's a Mono response but that information is not being used at the moment.
Comment From: rstoyanchev
The short answer, more of a limitation. We aim to support reactive types in Spring MVC but not all possible combinations.
In particular for ResponseEntity we need to decide between two handlers, HttpEntityMethodProcessor which handles regular ResponseEntity and ResponseBodyEmitterReturnValueHandler which handles various streaming and reactive type scenarios, also in combination with ResponseEntity. The challenge is that in supportsReturnType we can only use the MethodParameter information which in this case is very general and while we have something in place to check the Class from the actual value, we can't get any generic type information, which prevents us from recognizing it as a reactive body.
It's worth pointing out that ResponseEntity<Mono<?>> is somewhat unusual in the sense that usually you want to resolve the body and then decide on the overall response, otherwise the response is committed and you no longer can change the status in case the Mono completes with an error. It makes more sense to return and support Mono<ResponseEntity<?>> which gives the same result with more flexibility.
So while we do support this scenario, it does depend on having generic type information declared. This is something we could document better in this section.
Comment From: wilkinsona
Thanks, Rossen.
It's worth pointing out that
ResponseEntity<Mono<?>>is somewhat unusual in the sense that usually you want to resolve the body and then decide on the overall response, otherwise the response is committed and you no longer can change the status in case the Mono completes with an error.
Ah, of course. In that case, I don't think there's anything to be done here.