Beware of using spring WebClient(Episode6)
Let's imagine we received the WebClient response data and then binding to ResponseDto. Message value in ResponseDto can be null.
public class ResponseDto {
private String id;
@Nullable
private String message;
public String getMessage() {
return message;
}
}
If we try to get message in map
operator, we encounter a NPE.
webClientBuilder
// ... skip
.retrieve()
.bodyToMono(ResponseDto.class)
.map(responseDto -> responseDto.getMessage())
.onErrorResume(throwable -> {
return Mono.error(new RuntimeException(throwable));
});
That's not because responseDto is null. lambda body in the map
operator return null so mapper.apply(t)
result is null and go to catch scope. As a result the request go to onErrorResume.
We have three options to solve this problem. at first, we can use mapNotNull
operator instead of map
operator.
.mapNotNull(it -> it.getMessage())
mapNotNull
operator do work only if mapper.apply(t)
result is not null.
Second, do wrap Optional object to message getter.
public class ResponseDto {
private String id;
@Nullable
private String message;
public Optional<String> getMessage() {
return Optional.ofNullable(message);
}
}
Next, We use flatMap
and Mono.justOrEmpty
operators.
cf) We don't necessarily have to wrap getMessage to Optional
. Because Mono.justOrEmpty
argument can be not only Optional
but also null value.
webClientBuilder
// ... skip
.retrieve()
.bodyToMono(ResponseDto.class)
.flatMap(it -> Mono.justOrEmpty(it.getMessage()))
.onErrorResume(throwable -> {
return Mono.error(new RuntimeException(throwable));
});
Last third, We wrap Optional object to message getter. And then we use mapNotNull
operator instead of flatMap
operator. However we need .orElse(null)
operator additionally.
webClientBuilder
// ... skip
.retrieve()
.bodyToMono(ResponseDto.class)
.mapNotNull(it -> it.getMessage().orElse(null))
.onErrorResume(throwable -> {
return Mono.error(new RuntimeException(throwable));
});