How could we use @ExceptionHandler with spring web flux?
Asked Answered
G

4

19

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,

Grantham answered 6/3, 2018 at 18:2 Comment(0)
C
14

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).

Christianachristiane answered 6/3, 2018 at 20:22 Comment(3)
The Spring Framework reference will be updated soon, the ticket to track is 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.Lift
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?Grantham
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.Christianachristiane
A
15

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();
        }
    }
    
  3. Create a Controller

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

Example extracted here:

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

Averell answered 9/2, 2021 at 22:18 Comment(1)
Pretty sure this only works if you include spring MVC spring-boot-starter-web in your project.Horse
C
14

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).

Christianachristiane answered 6/3, 2018 at 20:22 Comment(3)
The Spring Framework reference will be updated soon, the ticket to track is 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.Lift
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?Grantham
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.Christianachristiane
N
5

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\""
}
Necolenecro answered 17/6, 2018 at 22:1 Comment(1)
could you add some code that explains what you mean?Thousandth
P
1

The following global error handler did the trick for me:

import org.springframework.web.server.ResponseStatusException;  
    
    @Slf4j
    @RestControllerAdvice
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
    public class MyCustomReactiveErrorHandling {
    
     @ExceptionHandler(MyCustomNotFoundException.class)
      public void handleMyCustomException(MyCustomNotFoundException ex) {
        throw new ResponseStatusException(404, "Data not found!", ex);
      }
    
    }

Throwing my exceptions returns the correct http status code at the rest service.

Paleography answered 22/3, 2022 at 14:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.