Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring controller advice does not correctly handle a CompletableFuture completed exceptionally

I am using Spring Boot 1.5, and I have a controller that executes asynchronously, returning a CompletableFuture<User>.

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private final UserService service;

    @GetMapping("/{id}/address")
    public CompletableFuture<Address> getAddress(@PathVariable String id) {
        return service.findById(id).thenApply(User::getAddress);
    }
}

The method UserService.findById can throw a UserNotFoundException. So, I develop dedicated controller advice.

@ControllerAdvice(assignableTypes = UserController .class)
public class UserExceptionAdvice {
    @ExceptionHandler(UserNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ResponseBody
    public String handleUserNotFoundException(UserNotFoundException ex) {
        return ex.getMessage();
    }
}

The problem is that tests are not passing returning an HTTP 500 status and not a 404 status in case of an unknown user request to the controller.

What's going on?

like image 709
riccardo.cardin Avatar asked Apr 05 '18 16:04

riccardo.cardin


People also ask

How to handle exception in CompletableFuture?

Exception handling is important when writing code with CompletableFuture . CompletableFuture provides three methods to handle them: handle() , whenComplete() , and exceptionally() . They look quite similar and it's easy to get lost when you are not familiar with the API.

How do you use exceptionally in Java?

Using exceptionally() methodSimply if there's no exception then exceptionally( ) stage is skipped otherwise it is executed. The exception passed to the exceptionally function is Completion Exception which wraps the actual exception as its root cause. future = future.


1 Answers

For those of you who still run into trouble with this : even though Spring correctly unwraps the ExecutionException, it doesn't work if you have a handler for the type "Exception", which gets chosen to handle ExecutionException, and not the handler for the underlying cause.

The solution : create a second ControllerAdvice with the "Exception" handler, and put @Order(Ordered.HIGHEST_PRECEDENCE) on your regular handler. That way, your regular handler will go first, and your second ControllerAdvice will act as a catch all.

like image 116
SurprisedFrog Avatar answered Nov 10 '22 02:11

SurprisedFrog