I have wrote following controller to handle all exception in my code:
#Controller
public class ErrorHandlerController {
#ExceptionHandler(value = Exception.class)
public String redirectToErrorPage(Model model){
model.addAttribute("message", "error on server");
return "errorPage";
}
}
But looks like following exception handler will work only if exception throws inside ErrorHandlerController
I have a huge count of controllers. Please advice me how to write one ExceptionHandler for all controllers ?
P.S.
I understand that I can use inheritance but I don't sure that it is the best decision.
Change the way you annotate your controller from #Controller to #ControllerAdvice that will make it a global exception handler
the docs say
The default behavior (i.e. if used without any selector), the
#ControllerAdvice annotated class will assist all known Controllers.
Also, you would have to change your method to something like
#ExceptionHandler(value = Exception.class)
public ModelAndView redirectToErrorPage(Exception e) {
ModelAndView mav = new ModelAndView("errorPage");
mav.getModelMap().addAttribute("message", "error on server");
return mav;
}
To understand why the model argument is not resolved in the method's annotated with #ExceptionHandler check out the going deeper part of the http://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
Related
I wrote my Spring Boot ProductController Class with productDetail method & handleMethodArgumentNotValid method . handleMethodArgumentNotValid method is annotated with #ExceptionHandler(MethodArgumentNotValid.class). It worked perfectly fine. After that I removed
handleMethodArgumentNotValid method from Controller class, as I would like to use #ControllerAdvice. But it is executing BaseException class of the project. It is not executing #ControllerAdvice method.
Here is my Controller class.
#PostMapping("/productDetail")
public void productDetail(#Valid #RequestBody ProductDetail productDetail) {
System.out.println("I am in Controller ProductDetail ....");
try {
iOrderService.updateProductDetail(productDetail);
} catch (Exception e) {
//Executes Base Exception class information here
...
}
}
Here is my ControllerAdvice .
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
#Override
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request
) {
same code that I had in handleMethodArgumentNotValid method of ProductController class here
ErrorResponse errorResponse = new ErrorResponse(
HttpStatus.UNPROCESSABLE_ENTITY.value(),
"Validation error. Check 'errors' field for details."
);
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
errorResponse.addValidationError(fieldError.getField(),
fieldError.getDefaultMessage());
}
return ResponseEntity.unprocessableEntity().body(errorResponse);
}
How can I handle MethodArgumentNotValidException so that it won't execute BaseException class?
Your global exception handler can only handle uncaught exceptions. So if you want it to handle anything thrown by iOrderService.updateProductDetail(productDetail);, you'll need to remove the try/catch.
I suspect that your test input to productDetail() is not actually causing a MethodArgumentNotValidException. Either that or your global exception handler is not included in your component scan. I'd recommend adding a "catchAll" method to your global exception handler for testing purposes. Just to see if it's catching any exceptions at all.
#ExceptionHandler(Exception.class)
protected ResponseEntity<ExceptionEnvelope> catchAll(Exception exception, WebRequest request) {
return buildResponse(HttpStatus.INTERNAL_SERVER_ERROR, exception, request);
}
Set a breakpoint in there and just see if you're able to hit it. I've had issues like this before and it ended up being that my assumptions were incorrect about which exceptions spring throws for different circumstances. Catching all exceptions like this will allow you to validate that the GlobalExceptionHandler is wired up properly, and will also tell you which exception is actually getting thrown.
I changed RestController annotation to #Controller annotation in Controller Class and annotated method with #ResponseBody and it worked.
Controller method:
public Mission createMission(final #Valid #RequestBody Mission mission) {
//save..
return mission;
}
I want to enumerate all validation error messages as json and return.
I can do that with creating a new exception class, adding BindingResult in controller method param, throwing the new exception when binding result has error and handling the exception in #ControllerAdvice class.
if (bindingResult.hasErrors()) {
throw new InvalidRequestException("Fix input and try again", bindingResult);
}
But I want to avoid checking BindingResult in each controller method, rather handle globally. I could have done that if I could handle exception: org.springframework.web.bind.MethodArgumentNotValidException. But unfortunately Spring boot does not allow
#ExceptionHandler(MethodArgumentNotValidException.class)
it shows ambiguous exception handler, as the same exception is handled ResponseEntityExceptionHandler, and I can't override handleException as it is final. I tried overriding protected ResponseEntity handleMethodArgumentNotValid() by catching Exception.class, but that does not get called at all.
Is there any way to achieve this goal? it seems like lots of things for a simple task :(
You should create a class that use #ControllerAdvice and extends ResponseEntityExceptionHandler. Then you can override the method handleMethodArgumentNotValid. You can use something like that:
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#Override
#ResponseStatus(HttpStatus.BAD_REQUEST)
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
final List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
Map <String, Set<String>> errorsMap = fieldErrors.stream().collect(
Collectors.groupingBy(FieldError::getField,
Collectors.mapping(FieldError::getDefaultMessage, Collectors.toSet())
)
);
return new ResponseEntity(errorsMap.isEmpty()? ex:errorsMap, headers, status);
}
}
If you don't want to extend to ResponseEntityExceptionHandler, you need to annotate your GlobalExceptionHandler to #ControllerAdvice and #ResponseBody.
Update:
Spring Boot created a new annotation #RestControllerAdvice in combination of #ControllerAdvice and #ResponseBody.
I found the problem, the controller advice were put in common util module, which does not seem to be used by Spring, though it was under the same base package of component scanning. What I did is overriding methods from ResponseEntityExceptionHandler in a global abstract class (in common module), then in other module just extended that class.
Whenever user types a wrong url in browser it results in NoSuchRequestHandlingMethodException I want to return a customize view rather than the default view that spring provides with a message. I have #AdviceController in which I tried to catch it through #ExceptionHAndler(NoSuchRequestHandlingMethodException.class) and return a view but it didn't work. I also inherited DefaultHandlerExceptionResolver and overidden the method that handles NoSuchRequestHandlingMethodException, this also didn't work. I also tried to register view names to exceptions through extending SimpleMappingExceptionResolver setExecptionMapping(in same #AdviceController class) method that also didn't one work. I don't know what I am doing wrong. Am I even right about that spring throws NoSuchRequestHandlingMethodException when it doesn't find any mappings in controller Can somebody tell me how to return custom views for Spring Exceptions that are matched with HTTP error codes.
This is my #ControllerAdvice
#ControllerAdvice
public class ExceptionHandlerController extends
DefaultHandlerExceptionResolver {
#ExceptionHandler(NoSuchRequestHandlingMethodException.class)
public String handleException(NoSuchRequestHandlingMethodException.class, Model model) {
String error = th.getMessage();
model.addAttribute("error", error);
model.addAttribute(new User());
return "login";
}
#override
protected ModelAndView handleNoSuchRequestHandlingMethodException
(NoSuchRequestHandlingMethodException ex, HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
ModelAndView mav = new ModelAndView("notfound");
return mav;
}
}
UPDATE: Solution
The reason that NoSuchRequestHandlingMethodException does not get raised is because Dispatcher Servlet delegates request to default servlet handler(in my case DefaultServletHttpRequestHandler) and considered it handled. The workaround that I have implemented is that I have created a handler method with mapping '/*' that is executed when there is no specific mapping found in any controller and there I through new NoSuchRequestHandlingMethodException which can be caught in #ExceptionHandler and then returned a custom view. However, I do not know the downside of handling exception like this. If any let me know.
#Controller
public class HomeController {
...
#RequestMapping(value = "/*", method = RequestMethod.GET )
public void handleAtLast(HttpServletRequest request) throws NoSuchRequestHandlingMethodException {
throw new NoSuchRequestHandlingMethodException(request);
}
}
ExceptionHandlerController.java
#ControllerAdvice
public class ExceptionHandlerController extends DefaultHandlerExceptionResolver {
#ExceptionHandler(NoSuchRequestHandlingMethodException.class)
public String handleNoSuchMethodtFoundException(NoSuchRequestHandlingMethodException ex){
return "error/notfound";
}
}
try this way
#ControllerAdvice
public class AppWideExceptionHandler {
#ExceptionHandler(Exception.class) // replace with your exception Class
public String errorHandler(){
System.out.println("caught exception");
return "home"; // your view
}
}
The reason that NoSuchRequestHandlingMethodException does not get raised is because Dispatcher Servlet delegates request to default servlet handler(in my case DefaultServletHttpRequestHandler) and considered it handled. The workaround that I have implemented is that I have created a handler method with mapping '/*' that is executed when there is no specific mapping found in any controller and there I through new NoSuchRequestHandlingMethodException which can be caught in #ExceptionHandler and then returned a custom view. However, I do not know the downside of handling exception like this. If any let me know.
#Controller
public class HomeController {
...
#RequestMapping(value = "/*", method = RequestMethod.GET )
public void handleAtLast(HttpServletRequest request) throws NoSuchRequestHandlingMethodException {
throw new NoSuchRequestHandlingMethodException(request);
}
}
ExceptionHandlerController.java
#ControllerAdvice
public class ExceptionHandlerController extends DefaultHandlerExceptionResolver {
#ExceptionHandler(NoSuchRequestHandlingMethodException.class)
public String handleNoSuchMethodtFoundException(NoSuchRequestHandlingMethodException ex){
return "error/notfound";
}
}
I have a Spring MVC application where I use data binding to populate a custom form object someForm with the posted values. The interesting part of the controller looks like the following:
#RequestMapping(value = "/some/path", method = RequestMethod.POST)
public String createNewUser(#ModelAttribute("someForm") SomeForm someForm, BindingResult result){
SomeFormValidator validator = new SomeFormValidator();
validator.validate(someForm, result);
if(result.hasErrors()){
...
return "/some/path";
}
}
SomeFormValidator class is implementing Springs org.springframework.validation.Validator interface. While this is great for validating the users input and creating error messages related to the input, this seems to be not well suited to handle more critical errors, which cannot be presented to the user but are still related to a controller input, like a missing hidden field which is expected to be present at post time. Such errors should result in application errors. What is the Spring MVC way to handle such errors?
What I usually do, I dont catch exceptions in the DAO and Service layes. I just throw them then I define ExceptionHandlers in the Controller class and in these ExceptionHandlers, I put my codes for handling such errors then redirect my users to a page saying something like
Fatal Error Occured. Please contact the administrators.
Here is the sample code of ExceptionHandler annotation
#Controller
public class MyController {
#Autowired
protected MyService myService;
//This method will be executed when an exception of type SomeException1 is thrown
//by one of the controller methods
#ExceptionHandler(SomeException1.class)
public String handleSomeException1(...) {
//...
//do some stuff
//...
return "view-saying-some-exception-1-occured";
}
//This method will be executed when an exception of type SomeException2 is thrown
//by one of the controller methods
#ExceptionHandler(SomeException2.class)
public String handleSomeException2(...) {
//...
//do some stuff
//...
return "view-saying-some-exception-2-occured";
}
//The controller method that will entertain request mappings must declare
//that they can throw the exception class that your exception handler catches
#RequestMapping(value = "/someUrl.htm", method = RequestMethod.POST)
public String someMethod(...) throws SomeException1, SomeException2{
//...
//do some stuff, call to myService maybe
//...
return "the-happy-path-view-name";
}
}
I know that Exception is the Parent of all exceptions but I thought when you set #ExceptionHandler with specific exception class this should handle that specific exception.
Maybe you can point what I have missed in following code so MethodArgumentNotValidException will go into processValidationError method not processError method.
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public ValidationErrorDTO processError(Exception e) {
return processErrors(e);
}
}
#ControllerAdvice
public class OtherExceptionHandler {
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ValidationErrorDTO processValidationError(MethodArgumentNotValidException ex) {
return processErrors(ex);
}
}
After your edit it's clear that you have more than one #ControllerAdvice class.
In short, the problem is that your ExceptionHandler class (and its #ExceptionHandler for Exception.class) gets registered first by Spring, and because Exception handler matches any exception, it will be matched before Spring ever gets to more specific handlers defined.
You can read detailed explanation in #Sotirios answer here.
I'd recommend you register only one ControllerAdvice and to make sure it extends ResponseEntityExceptionHandler, so the default handling of MethodArgumentNotValidException is not overwritten.
If you then wish to alter the logic of handling the MethodArumentNotValidException, you can override the handleMethodArgumentNotValid method.