Gotcha: checked and unchecked exception handling from Spring WebFlux WebClient

Featured image for sharing metadata for article

As noted in Replacing Text in Vim with the Output of a Command, it can be handy to pipe a bit of text into another command.

While working with Spring Webflux, I was writing tests to validate that the right business logic exceptions were being thrown by an HTTP call erroring, such as:

import static org.assertj.core.api.Assertions.assertThatThrownBy;

import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

class Example {
  @Test
  void example() {
    assertThatThrownBy(this::process).isInstanceOf(BusinessLogicException.class);
  }

  void process() {
    WebClient webClient = WebClient.builder().build();

    webClient
        .get()
        .uri("http://localhost:4010/")
        .retrieve()
        .onStatus(
            s -> s.equals(HttpStatus.NOT_FOUND), (e) -> Mono.error(new BusinessLogicException()))
        .toBodilessEntity()
        .block();
  }

  private static class BusinessLogicException extends Exception {}
}

Unfortunately, this doesn't work - and we see the following error from our failed assertion:

Expecting actual throwable to be an instance of:
  uk.gov.api.springboot.infrastructure.fetcher.v1alpha.E.BusinessLogicException
but was:
  reactor.core.Exceptions$ReactiveException: uk.gov.api.springboot.infrastructure.fetcher.v1alpha.E$BusinessLogicException
	at reactor.core.Exceptions.propagate(Exceptions.java:392)
	at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:97)
	at reactor.core.publisher.Mono.block(Mono.java:1707)
	...(64 remaining lines not displayed - this can be changed with Assertions.setMaxStackTraceElementsDisplayed)

Notice that there is actually a BusinessLogicException in there, but it appears to be wrapped in a ReactiveException.

This is because checked exceptions aren't allowed to be thrown on their won, so need to be a RuntimeException, which WebFlux does using the ReactiveException.

To solve this, we either need to make the following change, to relax our test:

   @Test
   void example() {
-    assertThatThrownBy(this::process).isInstanceOf(BusinessLogicException.class);
+    assertThatThrownBy(this::process).hasCauseInstanceOf(BusinessLogicException.class);
   }

Or to promote our exception to be a RuntimeException, so they're not wrapped in a ReactiveException:

-  private static class BusinessLogicException extends Exception {}
+  private static class BusinessLogicException extends RuntimeException {}

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the Apache License 2.0.

#blogumentation #spring #spring-boot #java #testing.

Also on:

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.