8

In spring web we could use annotation @ExceptionHandler for handling server and client errors for controllers.

I've tried to use this annotation with web-flux controller and it still worked for me, but after some investigation I've found out here

The situation with Spring Web Reactive is more complicated. Because the reactive streams are evaluted by a different thread than the one that executes the controllers method, the exceptions won’t be propagated to the controller thread automatically. This means that the @ExceptionHandler method will work only for exceptions that are thrown in the thread that handles the request directly. Exceptions thrown in the stream will have to be propagated back to the thread if we want to use the @ExceptionHandler feature. This seems like a bit of a let down but at the time of writing this Spring 5 is still not released so error handling might still get better.

So my question is how to propagate back exception to the thread. Is there a good example or article about using @ExceptionHandler and Spring web flux?

Updated: From spring.io it looks like it's supported, but still lack general understanding

Thanks,

Evgen
  • 998
  • 2
  • 9
  • 21

3 Answers3

8

You can use @ExceptionHandler annotated methods to handle errors that happen within the execution of a WebFlux handler (e.g., your controller method). With MVC you can indeed also handle errors happening during the mapping phase, but this is not the case with WebFlux.

Back to your exception propagation question, the article you're sharing is not accurate.

In reactive applications, the request processing can indeed hop from one thread to another at any time, so you can't rely on the "one thread per request" model anymore (think: ThreadLocal).

You don't have to think about exception propagation or how threads are managed, really. For example, the following samples should be equivalent:

@GetMapping("/test")
public Mono<User> showUser() {
  throw new IllegalStateException("error message!);
}


@GetMapping("/test")
public Mono<User> showUser() {
  return Mono.error(new IllegalStateException("error message!));
}

Reactor will send those Exceptions as error signals as expected in the Reactive Streams contract (see the "error handling" documentation section for more on that).

Brian Clozel
  • 46,620
  • 12
  • 129
  • 152
  • 1
    The Spring Framework reference will be updated soon, the ticket to track is https://jira.spring.io/browse/SPR-16394. Brian is right, the information in the blog post is incorrect. @ExceptionHandler should work as expected regardless of whether the exception occurs during controller method invocation, or (more likely) if the Mono produces an error. – Rossen Stoyanchev Mar 06 '18 at 20:34
  • Also could you specify what do you mean by "errors while mapping phase". Does validation considered error within controller or mapping phase? For example we can catch Javax annotation validation errors with WebExchangeBindException does it also works async? – Evgen Mar 07 '18 at 10:10
  • once the handler has been selected, you should be able to catch errors like validation (`@Validated` on a controller method parameter) or decoding (invalid JSON for example) with the `@ExceptionHandler` mechanism. – Brian Clozel Mar 07 '18 at 12:05
4

not an exact answer to the original question, but a quick way to map your exceptions to http response status is to throw org.springframework.web.server.ResponseStatusException / or create your own subclasses...

Full control over http response status + spring will add a response body with the option to add a reason.

{
    "timestamp": 1529138182607,
    "path": "/api/notes/f7b.491bc-5c86-4fe6-9ad7-111",
    "status": 400,
    "error": "Bad Request",
    "message": "For input string: \"f7b.491bc\""
}
Hartmut
  • 600
  • 8
  • 9
1

Now it is possible to use the @ExceptionHandler as well as @RestControllerAdvice or even @ControllerAdvice in Spring WebFlux.

Example:

  1. Add the webflux dependency

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webflux</artifactId>
    </dependency>
    
  2. Create your class ExceptionHandler

@RestControllerAdvice public class ExceptionHandlers {

private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandlers.class);

@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String serverExceptionHandler(Exception ex) {
    LOGGER.error(ex.getMessage(),ex);
    return ex.getMessage();
}

}

  1. Create a Controller

    @GetMapping(value = "/error",produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public Mono exceptionReturn(){ return Mono.error(new RuntimeException("test error")); }

Example extracted here:

https://ddcode.net/2019/06/21/spring-5-webflux-exception-handling/

Eddy Bayonne
  • 1,474
  • 13
  • 18
  • 1
    Pretty sure this only works if you include spring MVC spring-boot-starter-web in your project. – etech Apr 29 '21 at 10:15