WebFlux exception handling: onErrorReturn and onErrorResume

1 Origin

I’m learning WebFlux recently,
Some problems are encountered when handling exceptions. For example, exceptions thrown directly by Java cannot be directly caught by onErrorReturn and onErrorResume.
However, exceptions after methods such as map or flatMap can be caught directly.
So, I conducted a test and found that the exceptions that can be caught by onErrorReturn and onErrorResume are Throwable types or types wrapped by Mono or Flux.
When we use onErrorReturn and onErrorResume in WebFlux to catch exceptions, there are two ways:
(1) When a native Java runtime exception is thrown, you need to use Mono or Flux packaging: Mono.error or FluxError;
(2) Exceptions thrown in other native methods such as map or flatMap can be caught directly without packaging.
I share it below to help readers easily cope with knowledge exchange and assessment.

2 Exception handling

Exception handling is a very important part of software development.
In business systems, we need to capture exceptions and give appropriate description information to let the caller know which link the exception is.
In computing-related businesses, there are usually more requirements for the calculation results. For example, if an exception occurs within the program, the output of the calculation results cannot be affected. There needs to be a guaranteed calculation result, such as the advertising bid in the advertising recommendation system. Even if an exception occurs within the program, the final advertising bid must still be returned to ensure that the process can continue normally.
Of course, different business systems have different needs. Some need to directly expose exceptions, and some need to record but give guaranteed results. However, exceptions must all be handled.

2.1 WebFlux exception handling

Exception handling in WebFlux is different from exception handling in SpringMVC.
Same: global exception interception can be used;
Different: WebFlux can use custom exception handling methods, such as onErrorReturn and onErrorResume.
In WebFlux, the exceptions that can be handled by onErrorReturn and onErrorResume are wrapped by Mono or Flux.
It can be understood this way: Javathrows directly runtime exceptions, onErrorReturn and onErrorResumecannot be caught directly, and need to be passed Mono.error or Flux.errorWrapping can be caught normally. Of course, if global exception catching is enabled, there is no need to wrap.
Let’s take a look at the source code of onErrorReturn and onErrorResume:

onErrorReturn

onErrorReusme

It can be seen from the source code that onErrorResume is called in onErrorReturn. The difference is the input parameters and return value. The input parameters of onErrorReturn are fixed values, and the input parameters of onErrorResume are functions, and the processing logic can be customized. The parameter of onErrorResume is Functions. From this generic parameter, it can be seen that onErrorResume can only handle the type data of Throwable type and Mono and its subclasses. Therefore, it cannot directly capture Java native runtime exceptions. Java throwable exceptions are the parent class of Error and Exception. Therefore, any runtime exception cannot be caught by onErrorResume or onErrorReturn, unless it is wrapped with Mono.error or Flux.error before it can be caught normally.
Java abnormal relationship: https://blog.csdn.net/Xin_101/article/details/110210485

2.2 Sample

Prerequisite: Global exception capture is not enabled;

  • interface
@GetMapping("/mathematics/operation/flow/mono")
    @ApiOperation("Continuous flow test mono")
    public Mono<Response<Integer>> mathematicsOperationFlowUnderMono(@RequestParam Integer var1, @RequestParam Integer var2) {<!-- -->

        return mathematicsOperationService.divideMono(var1, var2)
                .onErrorReturn(10)
                .map(addResult -> {<!-- -->
                    logger.info(">>>>>>Add result:{}", addResult);
                    return Response.success(addResult);
                })
                .onErrorResume(ex -> {<!-- -->
                    logger.info(">>>>>>Error resume:", ex);
                    return Mono.just(Response.fail(-100));
                });
    }

2.2.1 Native runtime exception

Java native exceptions are errors and exceptions of Throwable subclasses: Error and Exception.
Take runtime exceptions as an example. When an exception occurs in the program, the result returned by the interface is WebFlux information, not the developer’s customized response template.

  • Exception handling
 @Override
    public Mono<Integer> divideMono(Integer var1, Integer var2) {<!-- -->
        try{<!-- -->
            return Mono.just(var1 / var2);
        } catch(Exception ex) {<!-- -->
            throw new RuntimeException("Custom runtime exception");
        }
    }

The information returned is as follows:

2.2.2 Mono packaging exception

onErrorReturn and onErrorResume can handle Mono or Flux wrapped exceptions,
The packaging is as follows:

  • Exception handling
 @Override
    public Mono<Integer> divideMono(Integer var1, Integer var2) {<!-- -->
        try{<!-- -->
            return Mono.just(var1 / var2);
        } catch(Exception ex) {<!-- -->
            return Mono.error(ex);
        }
    }

The wrapped exception can be captured by Error Return. The interface uses a fixed value: 10 in onErrorReturn, so the returned result is 10. The results are as follows:

2.2.3 Native Mono exception

As can be seen from the above, WebFlux can capture and handle exception information packaged by Mono or Flux.
Therefore, when we use map or flatMap to process data, we can directly use onErrorReturn and onErrorResume to catch exceptions. Exceptions generated in map and flatMap will be caught directly without packaging.
For testing, we added 1/0 to the map, which will throw an exception when running, and then catch the exception in onErrorResume and return -100.

 @GetMapping("/mathematics/operation/flow/mono")
    @ApiOperation("Continuous flow test mono")
    public Mono<Response<Integer>> mathematicsOperationFlowUnderMono(@RequestParam Integer var1, @RequestParam Integer var2) {<!-- -->

        return mathematicsOperationService.divideMono(var1, var2)
                .onErrorReturn(10)
                .onErrorResume(ex -> {<!-- -->
                    logger.info(">>>>>>Error resume -1:", ex);
                    return Mono.just(-1);})
                .map(addResult -> {<!-- -->
                    logger.info(">>>>>>Add result:{}", addResult);
                    int a = 1/0;
                    return Response.success(addResult);
                })
                .onErrorResume(ex -> {<!-- -->
                    logger.info(">>>>>>Error resume:", ex);
                    return Mono.just(Response.fail(-100));
                });
    }

When an exception occurs, onErrorResume will capture it directly and return customized data, such as -100 above,
The results are shown below.

3 Summary

(1) WebFlux can use onErrorReturn and onErrorResume to handle exceptions;
(2) The exceptions that can be handled by onErrorReturn and onErrorResume are Throwable types and exceptions packaged by Mono or Flux and their subclasses;
(3) Use Mono.error and Flux.error to wrap exceptions or handle exceptions after map or flatMap.