Affects: 5.2.13.RELEASE / Boot 2.3.9
What is wrong
Using java.util.Optional
(either as a class or ParameterizedTypeReference
) in toEntity()
of WebClient
does not return an Optional.empty()
but null
when a given http response body is missing.
How it should work
Empty http response bodies should create an Optional.empty()
instead of a null
value.
Research
I dug into the WebClients logic and noticed that an empty Mono is not processed with the resolved HttpMessageDecoder
(in my case, Jackson), e.g. in AbstractJackson2Decoder.decodeToMono
and later hardcodes to null
in WebClientUtils.mapToEntity
via some... interesting magic strings :)
Example
// given an endpoint is reachable that will return no value as a body:
Optional<?> result = webClient.method(method).uri(uri).retrieve().toEntity(Optional.class).block();
// result is null, not Optional.empty
Comment From: rstoyanchev
While we could make this work, it would be more idiomatic to use Mono#blockOptional
:
Optional<Foo> result = webClient.method(method).uri(uri).retrieve().toEntity(Foo.class).blockOptional();
Comment From: rstoyanchev
Generally where Mono
is exposed or supported, e.g. in annotated controller methods, we don't support Optional
as well because, because Mono
already expresses the concept of empty and provides a seamless path to related JDK types.
Comment From: roookeee
While the reasoning might be correct, even the slighest abstraction layer above WebClient
is forced to duplicate it's APIs for Optional
and non Optional
ParameterizedType<T>
which is quite cumbersome, especially when you build libraries upon WebClient
which don't want to leak that implementation detail. We currently have a workaround in place that "hacks" this behaviour by introspecting the supplied ParameterizedType<T>
which is non-ideal.
Hope this clears stuff up :)
Thank you for your time!
Comment From: rstoyanchev
The context is more clear, but without any knowledge about the higher level API, it's not too clear where the duplication comes from. For such embedded use of WebClient
, I imagine code paths trickling down to a single, generalized call to WebClient where an Optional wrapper can be handled as you described.
Adding support for Optional in all places where we decode to Flux<T>
or Mono<T>
could be a fairly extensive change, which is why I'm trying to make sure it is absolutely necessary.
Comment From: roookeee
Imagine a spring-data-repository
implementation that is using WebClient
internally. Instead of having one central function that does the HTTP calling we have to duplicate or do thorough type introspection (which isn't provided by Spring) to use different WebClient
functions.
Spring supports Optional
in a lot of places and it's just a surprising implementation detail which is why I created this issue: for consistencies sake.
That being said it seems like this is quite the niche issue. Throwing a lot of work at this seems unwise unless you also see merit in the broader implications of this (consistency, non-surprising APIs). Debugging this issue took quite some time so this issue existing is already worthwile.
Thank you for your time :)
Comment From: snicoll
It's been a while and there hasn't been additional interested by the community so I am going to close this now.