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. image.png

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. image.png

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