Robert Strauch
Robert Strauch

Reputation: 12906

Handling errors from Spring WebClient in another method

In a Spring Boot application, I'm using WebClient to invoke a POST request to a remote application. The method currently looks like this:

// Class A
public void sendNotification(String notification) {
    final WebClient webClient = WebClient.builder()
            .defaultHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
            .build();
    webClient.post()
            .uri("http://localhost:9000/api")
            .body(BodyInserters.fromValue(notification))
            .retrieve()
            .onStatus(HttpStatus::isError, clientResponse -> Mono.error(NotificationException::new))
            .toBodilessEntity()
            .block();
    log.info("Notification delivered successfully");
}

// Class B
public void someOtherMethod() {
    sendNotification("test");
}

The use case is: A method in another class calls sendNotification and should handle any error, i.e. any non 2xx status or if the request couldn't even be sent.

But I'm struggling with the concept of handling errors in the WebClient. As far as I understood, the following line would catch any HTTP status other than 2xx/3xx and then return a Mono.error with the NotificationException (a custom exception extending Exception).

onStatus(HttpStatus::isError, clientResponse -> Mono.error(NotificationException::new))

But how could someOtherMethod() handle this error scenario? How could it process this Mono.error? Or how does it actually catch the NotificationException if sendNotification doesn't even throw it in the signature?

Upvotes: 6

Views: 50619

Answers (2)

lkatiforis
lkatiforis

Reputation: 6255

Using imperative/blocking style you can surround it with a try-catch:

try {
    webClient.post()
            .uri("http://localhost:9000/api")
            .body(BodyInserters.fromValue(notification))
            .retrieve()
            .onStatus(HttpStatus::isError, clientResponse -> Mono.error(NotificationException::new))
            .toBodilessEntity()
            .block();
} catch(NotificationException e) {...}

A reactive solution would be to use the onErrorResume operator like this:

    webClient.post()
            .uri("http://localhost:9000/api")
            .body(BodyInserters.fromValue(notification))
            .retrieve()
            .onErrorResume(e -> someOtherMethod())
            .toBodilessEntity();

Here, the reactive method someOtherMethod() will be executed in case of any error.

Upvotes: 4

Auktis
Auktis

Reputation: 545

Well, there are many ways to handle errors, it really depends on what you want to do in case of an error.

In your current setup, the solution is straightforward: first, NotificationException should extend RuntimeException, thus, in case of an HTTP error, .block() will throw a NotificationException. It is a good practice to add it in the signature of the method, accompanied with a Javadoc entry.
In another method, you just need to catch the exception and do what you want with it.

/**
 * @param notification
 * @throws NotificationException in case of a HTTP error
 */
public void sendNotification(String notification) throws NotificationException {
    final WebClient webClient = WebClient.builder()
        .defaultHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
        .build();
    webClient.post()
        .uri("http://localhost:9000/api")
        .body(BodyInserters.fromValue(notification))
        .retrieve()
        .onStatus(HttpStatus::isError, clientResponse -> Mono.error(NotificationException::new))
        .toBodilessEntity()
        .block();
    log.info("Notification delivered successfully");
}

public void someOtherMethod() {
    try {
        sendNotification("test");
    } catch (NotificationException e) {
        // Treat exception
    }
}

In a more reactive style, you could return a Mono and use onErrorResume().

public Mono<Void> sendNotification(String notification) {
    final WebClient webClient = WebClient.builder()
        .defaultHeader(CONTENT_TYPE, APPLICATION_JSON_VALUE)
        .build();
    return webClient.post()
        .uri("http://localhost:9000/api")
        .body(BodyInserters.fromValue(notification))
        .retrieve()
        .onStatus(HttpStatus::isError, clientResponse -> Mono.error(NotificationException::new))
        .bodyToMono(Void.class);
}

public void someOtherMethod() {
    sendNotification("test")
        .onErrorResume(NotificationException.class, ex -> {
            log.error(ex.getMessage());
            return Mono.empty();
        })
        .doOnSuccess(unused -> log.info("Notification delivered successfully"))
        .block();
}

Upvotes: 13

Related Questions