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?
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With