Project Reactor - Error Handling Examples

If you're using Project Reactor and you need to handle error thrown by a method that returns Reactor's reactive type, you'll find about it on this tutorial.

For example, there is a method that always throws error as shown below

  private Mono myMethod() {
      return Mono.error(new MyCustomException("My error"));
  }

There are four useful operators in Reactor for handling error: doOnError, onErrorMap, onErrorReturn, and onErrorResume. All of them have different usage, but they have a similarity. Those methods allows us to filter which errors it should handle, either by passing the error class to be handled as the first argument or by passing Predicate as the first argument.

If you need to handle different kinds of errors in different ways, you can call the operator multiple times in chain. For example, if you want to use onErrorReturn and you need to return different values for different errors, you can do like this:

  Mono.defer(() -> myClass.myMethod())
                 .onErrorReturn(MyFirstException.class, "First fallback value")
                 .onErrorReturn(MyCustomException.class, "Fallback value")
                 .subscribe(System.out::println)

Because the method throws MyCustomException, the first onErrorReturn will be ignored as it doesn't match.

Using doOnError

doOnError will be executed when an error is thrown and hasn't been caught. If the error has already been caught beforehand, it will not be executed. For example, the code below will print the error message (instead of the default which prints stack trace). However, the error is not caught which means you can still get the error on the next operator in chain.

  Mono.defer(() -> myClass.myMethod())
                 .doOnError(e -> e.getMessage().equals("My error"), e -> System.err.println("err:" + e.getMessage()))
                 .subscribe(System.out::println)

Filter by class:

  Mono.defer(() -> myClass.myMethod())
                 .doOnError(MyCustomException.class, e -> System.err.println("err:" + e.getMessage()))
                 .subscribe(System.out::println)

Filter by Predicate

  Mono.defer(() -> myClass.myMethod())
                 .doOnError(e -> System.err.println("err:" + e.getMessage()))
                 .subscribe(System.out::println)

Using onErrorMap

onErrorMap is used to map an error into another error. As it's only being mapped, the error is still thrown.

  Mono.defer(() -> myClass.myMethod())
                 .onErrorMap(original -> new MyAnotherException("Not found"))
                 .subscribe(System.out::println)

Filter by class:

  Mono.defer(() -> myClass.myMethod())
                 .onErrorMap(MyCustomException.class, original -> new MyAnotherException("Not found1"))
                 .subscribe(System.out::println)

Filter by Predicate

  Mono.defer(() -> myClass.myMethod())
                 .onErrorMap(e -> e.getMessage().equals("My error"), original -> new MyAnotherException("Not found3"))
                 .subscribe(System.out::println)

Using onErrorReturn

With onErrorReturn, we can set a fallback value that will be returned if error is thrown. The next operator in the chain will get the fallback value instead of error.

  Mono.defer(() -> myClass.myMethod())
                 .onErrorReturn("Fallback value")
                 .subscribe(System.out::println)

Filter by class:

  Mono.defer(() -> myClass.myMethod())
                 .onErrorReturn(e -> e.getMessage().equals("My error"), "Fallback value")
                 .subscribe(System.out::println)

Filter by Predicate

  Mono.defer(() -> myClass.myMethod())
                 .onErrorReturn(MyCustomException.class, "Fallback value")
                 .subscribe(System.out::println)

Using onErrorResume

With onErrorReturn, we can set a fallback method that will be executed if error is thrown. The next operator in the chain will get the result of the fallback method instead of error.

  Mono.defer(() -> myClass.myMethod())
                 .onErrorResume(e -> myClass.fallbacKMethod())
                 .subscribe(System.out::println)

Filter by class:

  Mono.defer(() -> myClass.myMethod())
                 .onErrorResume(MyCustomException.class, e -> myClass.fallbacKMethod())
                 .subscribe(System.out::println)

Filter by Predicate

  Mono.defer(() -> myClass.myMethod())
                 .onErrorResume(e -> e.getMessage().equals("My error"), e -> myClass.fallbacKMethod())
                 .subscribe(System.out::println)

That's how to handle error in Project Reactor. If necessary, you can also do retry if error is thrown.