Working with multiple #ControllerAdvice classes - spring

I recently start to work with a #ControllerAdvice class to manage the exceptions in my Spring project. My current implementation is something like this:
#ControllerAdvice
public class GlobalDefaultExceptionHandler {
#ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null) throw e;
return new ModelAndView("error/5xx", "exception", e);
}
}
My next step should be handle more exceptions, but for this I am thinking of use multiple classes with #ControllerAdvice, one for http status code. My goal is make the methods of my controller which handle the form submissions redirect the user for some of my custom status pages (I have one for each group - 1xx, 2xx, 3xx, 4xx, 5xx).
That methods have a structure similar to this:
#RequestMapping(value="cadastra")
#PreAuthorize("hasPermission(#user, 'cadastra_'+#this.this.name)")
public String cadastra(Model model) throws InstantiationException, IllegalAccessException {
model.addAttribute("command", this.entity.newInstance());
return "private/cadastrar";
}
Anyone can tell me if this is a good approach and give some hint of how implement my controller methods to accomplish what I want?

You can have multiple #ControllerAdvice classes that handle different exceptions.
However, because you are handling the Exception.class on your GlobalDefaultExceptionHandler, any exception might be swallowed by it.
The way I got around this was to add #Order( value = Ordered.LOWEST_PRECEDENCE )
on my general exception handler and #Order( value = Ordered.HIGHEST_PRECEDENCE ) on the others.

Maybe you want to define specific exception classes (thrown by your controller, for example: NoResourceFoundException or InvalidResourceStatusException and so on) so your ExceptionController can seperate the different cases and redirect them to the proper status page.

Related

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?
The problem is due to how a completed exceptionally CompletableFuture handles the exception in subsequent stages.
As stated in the CompletableFuture javadoc
[..] if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause. [..]
In my case, the thenApply method creates a new instance of CompletionStage that wraps with a CompletionException the original UserNotFoundException :(
Sadly, the controller advice does not perform any unwrapping operation. Zalando developers also found this problem: Async CompletableFuture append errors
So, it seems to be not a good idea to use CompletableFuture and controller advice to implement asynchronous controllers in Spring.
A partial solution is to remap a CompletableFuture<T> to a DeferredResult<T>. In this blog, an implementation of a possible Adapter was given.
public class DeferredResults {
private DeferredResults() {}
public static <T> DeferredResult<T> from(final CompletableFuture<T> future) {
final DeferredResult<T> deferred = new DeferredResult<>();
future.thenAccept(deferred::setResult);
future.exceptionally(ex -> {
if (ex instanceof CompletionException) {
deferred.setErrorResult(ex.getCause());
} else {
deferred.setErrorResult(ex);
}
return null;
});
return deferred;
}
}
So, my original controller would change to the following.
#GetMapping("/{id}/address")
public DeferredResult<Address> getAddress(#PathVariable String id) {
return DeferredResults.from(service.findById(id).thenApply(User::getAddress));
}
I cannot understand why Spring natively supports CompletableFuture as return values of a controller, but it does not handle correctly in controller advice classes.
Hope it helps.
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.

Handling error scenarios in Spring REST

IMHO exceptions are for exceptional cases. Exceptions should not be thrown if the scenario can be handled without exception.
Creating exception takes at least 1ms and it comes with a performance impact. So which is the best way to handle error scenario?
Scenario#1:
ResponseEntity createOrder(#RequestBody Order order){
if(order.items == null)
return ResponseEntity.badRequest().build();
...
}
Scenario#2:
Spring provides #ControllerAdvice and ResponseEntityExceptionHandler as mentioned in Error Handling for REST with Spring
ResponseEntity createOrder(#RequestBody Order order){
if(order.items == null)
throw new CustomException();
...
}
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { CustomException.class })
protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) {
String bodyOfResponse = "Error";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.BAD_REQUEST, request);
}
}
Personally i would choose scenario #2 because it's centralized. Later you would be able to change response code for that particular exception or add some verbose logging. In terms of performance scenario #1 is obviously faster, but i would neglect that time difference
Well, in the particular case you have I would use Hibernate to validate and not let the invalid data into the controller to start with.
public class Order {
...
#NotNull
#NotEmpty
private List<Integer> items;
}
It will automatically create 400 error for the case items is either empty or null (internally uses exceptions though).
To handle other exceptions I would use #ExceptionHandler as you have there, either per controller or via a #ControllerAdvice (to catch globally).
Creating exception takes at least 1ms
Exceptions are slow, but not that slow.

Can I get Spring Validation errors in prehandle

I currently have something similar to this in all of my endpoints in my spring app.
if(bindingResult.hasErrors()){
return new ResponseEntity<>(BAD_REQUEST);
}
I would like to move this to a http interceptor so that I only need it in one place. However, I cannot figure out how to get all of the errors from the binding result in preHandle.
How would I get validation errors in preHandle, or some other time before it starts the actual route?
One way to achieve what I think you're looking for is to not include BindingResult as a method parameter. Given no BindingResult is included as a method argument Spring will throw a BindException exception. You can define an ExceptionHandler, generally I've placed these within a #ControllerAdvice, to handle the exception as needed. Below is some sample code
Controller
#PostMapping
public SomeReturnObject someMethod(#Valid SomeCommand command) {
// logic - no longer contains checks for binding result errors
}
As part of ControllerAdvice
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ApplicationControllerAdvice {
....
#ExceptionHandler(BindException.class)
#ResponseBody
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
protected SomeResponse handleBindException(BindException ex) {
// handle exception
}
}

Prevent spring errors on webpage

I am using spring MVC 3.
I validate various users input and show errors as applicable.
But this often to show the spring errors like org.springframework.core.convert.ConversionFailedException etc being shown on UI. How can i prevent the output of these errors on webpage ?
Note:
I understand that for topic starter my answer may be no longer
relevant. But it can be useful for those who have visited this page to
search for solutions to similar problems.
Answer:
In order to prevent the output of errors on web page, you may handle them. There are several types of error handling, that you may use for this in Spring MVC 3.x and above:
Controller-based exception handling
Global exception handling
Other methods, that are bit more complicated
Controller-based exception handling
You can add an #ExceptionHandler annotation on methods inside a controller. Such methods will function as error handlers for exceptions thrown from methods annotated as #RequestMapping in the same controller.
#ExceptionHandler(Exception.class)
public ModelAndView handleError(HttpServletRequest req, Exception e) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("exception", e);
modelAndView.addObject("url", req.getRequestURL());
modelAndView.setViewName("error");
return modelAndView;
}
Global exception handling
A controller advice allows you to apply exception handling across the whole application, not just to an individual controller. In other words, handling will apply to exceptions thrown from any controller.
#ControllerAdvice
class GlobalControllerExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
#ExceptionHandler(Exception.class)
public void handleError(HttpServletRequest req, Exception e) {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("exception", e);
modelAndView.addObject("url", req.getRequestURL());
modelAndView.setViewName(DEFAULT_ERROR_VIEW);
return modelAndView;
}
}
For more info:
Exception Handling in Spring MVC

Exception handler for REST controller in spring

I want to handle exceptions so the URL information is automatically shown to the client. Is there an easy way to do this?
<bean id="outboundExceptionAdapter" class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver">
<!-- what property to set here? -->
</bean>
You have two choices:
Spring Reference 15.9.1 HandlerExceptionResolver
Spring HandlerExceptionResolvers ease the pain of unexpected
exceptions that occur while your request is handled by a controller
that matched the request. HandlerExceptionResolvers somewhat resemble
the exception mappings you can define in the web application
descriptor web.xml. However, they provide a more flexible way to
handle exceptions. They provide information about which handler was
executing when the exception was thrown. Furthermore, a programmatic
way of handling exceptions gives you more options for responding
appropriately before the request is forwarded to another URL (the same
end result as when you use the servlet specific exception mappings).
The HandlerExceptionResolver has one method, containing everything you need:
HandlerExceptionResolver.resolveException(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex)
Or if you need different handlers for different controllers: Spring Reference Chapter 15.9.2 #ExceptionHandler
#ExceptionHandler(IOException.class)
public String handleIOException(IOException ex, HttpServletRequest request) {
return "every thing you asked for: " + request;
}
Short question short answer
I'm doing the following trick:
#ExceptionHandler(Exception.class)
public ModelAndView handleMyException(Exception exception) {
ModelAndView mv = new ModelAndView("redirect:errorMessage?error="+exception.getMessage());
return mv;
}
#RequestMapping(value="/errorMessage", method=RequestMethod.GET)
#Responsebody
public String handleMyExceptionOnRedirect(#RequestParamter("error") String error) {
return error;
}
Works flawless.

Resources