'Can't handle bad request using doOnError WebFlux

I wanna send some DTO object to server. Server have "Valid" annotation, and when server getting not valid DTO, he should send validation errors and something like "HttpStatus.BAD_REQUEST", but when I'm trying to send HttpStatus.BAD_REQUEST doOnError just ignore it.

POST-request from client

                    BookDTO bookDTO = BookDTO
                            .builder()
                            .author(authorTf.getText())
                            .title(titleTf.getText())
                            .publishDate(LocalDate.parse(publishDateDp.getValue().toString()))
                            .owner(userAuthRepository.getUser().getLogin())
                            .fileData(file.readAllBytes())
                            .build();
                        webClient.post()
                                .uri(bookAdd)
                                .contentType(MediaType.APPLICATION_JSON)
                                .bodyValue(bookDTO)
                                .retrieve()
                                .bodyToMono(Void.class)
                                .doOnError(exception -> log.error("Error on server - [{}]", exception.getMessage()))
                                .onErrorResume(WebClientResponseException.class, throwable -> {
                                    if (throwable.getStatusCode() == HttpStatus.BAD_REQUEST) {
                                        log.error("BAD_REQUEST!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); --My log doesn't contain this error, but server still has errors from bindingResult
                                        return Mono.empty();
                                    }

                                    return Mono.error(throwable);
                                })
                                .block();

Server-part

@PostMapping(value = "/add", consumes = {MediaType.APPLICATION_JSON_VALUE})
public HttpStatus savingBook(@RequestBody @Valid BookDTO bookDTO, BindingResult bindingResult) {
    List<FieldError> errors = bindingResult.getFieldErrors();
    if (bindingResult.hasErrors()) {
        for (FieldError error : errors ) {
            log.info("Client post uncorrected data [{}]", error.getDefaultMessage());
        }
        return HttpStatus.BAD_REQUEST;
    }else{libraryService.addingBookToDB(bookDTO);}
    return null;
}


Solution 1:[1]

doOnError is a so-called side effect operation that could be used for instrumentation before onError signal is propagated downstream. (e.g. to log error).

To handle errors you could use onErrorResume. The example, the following code handles the WebClientResponseException and returns Mono.empty instead.

...
.retrieve()
.doOnError(ex -> log.error("Error on server: {}", ex.getMessage()))
.onErrorResume(WebClientResponseException.class, ex -> {
    if (ex.getStatusCode() == HttpStatus.BAD_REQUEST) {
        return Mono.empty();
    }

    return Mono.error(ex);
})
...

As an alternative as @Toerktumlare mentioned in his comment, in case you want to handle http status, you could use onStatus method of the WebClient

...
.retrieve()
.onStatus(HttpStatus.BAD_REQUEST::equals, res -> Mono.empty())
...

Update

While working with block it's important to understand how reactive signals will be transformed.

  • onNext(T) -> T in case of Mono and List<T> for Flux
  • onError -> exception
  • onComplete -> null, in case flow completes without onNext

Here is a full example using WireMock for tests

class WebClientErrorHandlingTest {
    private WireMockServer wireMockServer;

    @BeforeEach
    void init() {
        wireMockServer = new WireMockServer(wireMockConfig().dynamicPort());
        wireMockServer.start();
        WireMock.configureFor(wireMockServer.port());
    }

    @Test
    void test() {
        stubFor(post("/test")
                .willReturn(aResponse()
                        .withHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                        .withStatus(400)
                )
        );

        WebClient webClient = WebClient.create("http://localhost:" + wireMockServer.port());

        Mono<Void> request = webClient.post()
                .uri("/test")
                .retrieve()
                .bodyToMono(Void.class)
                .doOnError(e -> log.error("Error on server - [{}]", e.getMessage()))
                .onErrorResume(WebClientResponseException.class, e -> {
                    if (e.getStatusCode() == HttpStatus.BAD_REQUEST) {
                        log.info("Ignoring error: {}", e.getMessage());
                        return Mono.empty();
                    }

                    return Mono.error(e);
                });

        Void response = request.block();

        assertNull(response);
    }
}

The response is null because we had just complete signal Mono.empty() that was transformed to null by applying block

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1