Partner – Microsoft – NPI (cat= Spring)
announcement - icon

Azure Spring Apps is a fully managed service from Microsoft (built in collaboration with VMware), focused on building and deploying Spring Boot applications on Azure Cloud without worrying about Kubernetes.

And, the Enterprise plan comes with some interesting features, such as commercial Spring runtime support, a 99.95% SLA and some deep discounts (up to 47%) when you are ready for production.

>> Learn more and deploy your first Spring Boot app to Azure.

You can also ask questions and leave feedback on the Azure Spring Apps GitHub page.

1. Overview

Error handling is a critical aspect of reactive programming with Spring WebFlux. Developers usually rely on two primary methods for error handling: throwing exceptions or using the Mono.error() method provided by Project Reactor. Both approaches are used to signal errors, but they have distinct characteristics and use cases.

In this tutorial, we will explain the differences between throwing an exception and Mono.error() in Spring WebFlux. We’ll provide illustrative Java code examples to make it more understandable.

2. Traditional Approach: Throwing Exceptions

For many years, throwing exceptions has been a reliable way of managing errors in Java applications. It is a simple way to interrupt the regular flow of the program and convey errors to the application’s higher layers. Spring WebFlux integrates smoothly with this conventional error-handling method, enabling developers to throw exceptions in their reactive endpoints. The code below represents an example of the conventional approach:

public Mono<User> getUserByIdThrowingException(String id) {
    User user = userRepository.findById(id);
    if (user == null) {
       throw new NotFoundException("User Not Found");
    }
    return Mono.justOrEmpty(user);
}

In this particular scenario, the getUserByIdThrowingException() method attempts to retrieve user data based on the ID provided by the UserRepository. If the user cannot be found, the method throws a NotFoundException, which signals the error within the reactive pipeline.

To perform unit testing, we import the assertThrows method from org.junit.jupiter.api.Assertions. This tests if getUserByIdThrowingException() throws a NotFoundException for users that are not found in the database. We use assertThrows with a lambda to execute the method call that should throw an exception.

If an exception is thrown, the code verifies that the thrown exception is of the expected type. However, if the method does not throw an exception, the test fails:

@Test
public void givenNonExistUser_whenFailureCall_then_Throws_exception() {
    assertThrows(
        NotFoundException.class,
        () -> userService.getUserByIdThrowingException("3")
    );
}

3. Embracing Reactivity: Mono.error()

In contrast to the traditional approach of throwing exceptions, Project Reactor introduces a reactive alternative through the Mono.error() method. This method generates a Mono that immediately terminates with an error signal, aligning seamlessly with the reactive programming paradigm.

Let’s examine the modified example utilizing Mono.error():

public Mono<User> getUserByIdUsingMonoError(String id) {
    User user = userRepository.findById(id);
    return (user != null)
      ? Mono.justOrEmpty(user)
      : Mono.error(new NotFoundException("User Not Found"));
}

To maintain a smooth user experience and consistent reactive flow, we use Mono.error() instead of throwing exceptions directly for unfound users in the database.

Here is the unit test for this method:

@Test
 public void givenNonExistUser_whenFailureCall_then_returnMonoError() {
    Mono result = userService.getUserByIdUsingMonoError("3");
    StepVerifier.create(result)
      .expectError(NotFoundException.class)
      .verify();
}

4. Understanding Key Differences and Use Cases

4.1. Control Flow Disruption

We use exceptions with try-catch or reactive operators like onErrorResume, onErrorReturn, or onErrorMap when signaled by Mono.error().

4.2. Laziness

Mono.error() now supports lazy instantiation of exceptions, which is beneficial in scenarios where constructing the exception involves resource-intensive operations.

4.3. Reactive Error Handling

Mono.error() aligns well with reactive programming paradigms, facilitating reactive error handling within the reactive stream.

5. Conclusion

In this article, we have discussed the fundamental differences between throwing exceptions and utilizing Mono.error() in Spring WebFlux for error handling in reactive applications; although both approaches serve the same purpose of signaling errors, they differ significantly in their control flow and integration with reactive pipelines.

Throwing exceptions interrupts the execution flow and transfers control to the nearest exception handler, making it suitable for imperative code paths. Conversely, Mono.error() seamlessly integrates with reactive streams, enabling asynchronous error signaling without halting the execution flow.

When developing reactive applications using Spring WebFlux, choosing the right error-handling mechanism is crucial based on the context and requirements. We use Mono.error() in reactive pipelines to maintain their reactive nature, and we use exceptions for imperative code paths. As always, the source code for this tutorial is available on GitHub.

Course – LS (cat=Spring)

Get started with Spring and Spring Boot, through the Learn Spring course:

>> THE COURSE
res – Junit (guide) (cat=Reactive)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.