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));
});