How to handle validation and critical errors in Spring MVC - java

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";
}
}

Related

Custom validators for nested classes and sending errors back to Controller

I'm working with Java validation and Spring. I have several classes that need validation set up. When it comes to validating simple fields of each class, my validators work fine. However, the field for one class contains a List of two other classes I need validated, and one of those contains a list of two other classes I need validated.
I found a way to get this working, but it is a bit messy and requires (what I think) is too much extra code in my Controller. For example, I made a separate method in my validation to validate the list of Class A:
public Errors validateClassA(ClassA classA){
List<ClassB> classBs = classA.getScanResults().getClassesToValidate();
for (ClassB classB : classBs) {
Errors error = new BeanPropertyBindingResult(classB, "ClassB");
classBValidator.validate(classB, error);
if(error.hasErrors()){
return error;
}
}
return null;
}
This works just for testing, but I don't want to pass back a null value and it seems a little bit messy. Here's the portion of my Controller using this code:
classAValidator.validate(classA, bindingResult);
Errors classBErrors = classAValidator.validateClassB(classA);
if (bindingResult.hasErrors()) {
classAValidator.logErrors(bindingResult);
return new ResponseEntity<GenericServiceResponse<Void>>(new GenericServiceResponse<Void>(FAIL, bindingResult.toString()), HttpStatus.BAD_REQUEST);
}
if(classBErrors.hasErrors()){
classAValidator.logErrors(classBErrors);
return new ResponseEntity<GenericServiceResponse<Void>>(new GenericServiceResponse<Void>(FAIL, classBErrors.toString()), HttpStatus.BAD_REQUEST);
}
Feels like I'm making this more complicated than it needs to be, but I'm not sure how to approach it. From a brief glance at the documentation for the Errors class, it looks like there is some way to set nested routes for the errors, but I couldn't seem to find many examples of that so I'm not sure if it would apply in my situation. Hope all of this makes sense and thanks for any suggestions.
Yeah, you are making this issue more complicated that it needs to be :) You do not need to implement these functionalities by yourself - Spring does it for you.
Let's assume you have a simple DTO like the one below:
public class SomeDTO {
public static class SomeOtherDTO {
#NotEmpty
private String someOtherField;
}
#NotNull
private SomeOtherDTO someField;
}
and #RestController with method:
#RequestMapping(value = "/someEndpoint", method = RequestMethod.POST)
public ResponseEntity<HttpStatus> someEndpoint(#Valid #RequestBody SomeDTO dto) {
return new ResponseEntity<>(HttpStatus.OK);
}
This is all - dto will now be validated by Spring - all you have to do is handle potential errors yourself. You can do it many different ways. My suggestion is to use #ControllerAdvice - that way you have unified approach to validation error handling across all registered #Controllers:
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception, HttpHeaders headers, HttpStatus status, WebRequest request) {
// here you have access to the errors via exception argument - do with them whatever you want
}
}

How does spring manage ExceptionHandler priority?

Giving this controller
#GetMapping("/test")
#ResponseBody
public String test() {
if (!false) {
throw new IllegalArgumentException();
}
return "blank";
}
#ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(Exception.class)
#ResponseBody
public String handleException(Exception e) {
return "Exception handler";
}
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ExceptionHandler(IllegalArgumentException.class)
#ResponseBody
public String handleIllegalException(IllegalArgumentException e) {
return "IllegalArgumentException handler";
}
Both ExceptionHandler match the IllegalArgumentException because it's a child of Exception class.
When I reach /test endpoint, the method handleIllegalException is called. If I throw a NullPointerException, the method handleException is called.
How does spring knows that it should execute the handleIllegalException method and not the handleException method ? How does it manage the priority when multiple ExceptionHandler match an Exception ?
(I thought the order or the ExceptionHandler declarations was important, but even if I declare handleIllegalException before handleException, the result is the same)
Spring MVC provides many different methods for Exception handling definitions.
In general, it will try to find the most "specific" exception handler registered to handle the exception. If there is no such a handler, it will try to check for the superclass of exception, maybe there is a handler for it, if it's not found as well, it will go one more level up and so on and so forth, from the most specific to the most general.
If you want to see it in the code of Spring, an entry point to learn this topic would be:
org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
This class resolves the exceptions registered through #ExceptionHandler methods out of the beans registered in the application context. This class, in turn, uses the another class org.springframework.web.method.annotation.ExceptionHandlerMethodResolver
which is responsible for mapping all the methods marked with #ExceptionHandler annotation.

How can I make a Spring Converter throw a custom exception instead of a ConversionFailedException?

I have a bunch of custom spring converters in my codebase something like:
public class ElasticSearchConverter implements Converter<RequestModel, ResponseModel> {
#Override
public final ResponseModel convert(RequestModel requestModel) {
if(!requestModel.isValid()) {
throw new ElasticSearchException("Request Model is not valid");
}
... Implement converter
}
}
I call those converters from a service by using the spring ConversionService
#Service
public class ElasticService {
#Autowired
private ConversionService conversionService;
public ResponseModel getResponse(RequestModel requestModel) {
//Throws ConversionFailedException instead of ElasticSearchException
ResponseModel responseModel = conversionService.convert(requestModel, ResponseModel.class);
return responseModel;
}
}
My problem is when I throw my ElasticSearchException inside my ElasticSearchConverter it gets caught inside the spring ConversionUtils class and converted to a ConversionFailedException. I want to catch the specific ElasticSearchException that I'm throwing in my converter.
How can I catch a specific exception from a spring Converter class in my service class?
You need to implement class that will handle your Exceptions
#ControllerAdvice
public class ExceptionTranslator {
#ExceptionHandler(ConversionFailedException.class) //handle your Exception
#ResponseStatus(HttpStatus.BadRequest) // Define the status you want to return
#ResponseBody
public ErrorDTO processElasticSearchException(ConversionFailedException ex) {
return new ErrorDTO();
/* Format your response as you need*/
}
}
#ControllerAdvice "Classes with (the annotation) can be declared explicitly as Spring beans or auto-detected via classpath scanning" show documentation
#ExceptionHandler defines the exception you want to catch
#ResponseStatus defines the http response status
#ResponseBody Serialize automatically response as json object
For my projects, i define an ErrorDTO to format the response, you can do the same and you will just have to construct your object and to return it
You can also put the code you want to execute into this method and rise an other exception if needed
You could write a simple adapter class that wraps the Spring ConversionService. In that class you would have a convert() method that delegates to the wrapped ConversionService method in a try/catch, catch the ConversionFailedException, inspect it (e.g. using getRootCause()) and rethrow as the exception of your choice. Then for all classes that would otherwise use ConversionService you would use your wrapper class instead.
You're violating the Single Responsibility Principle. It's not the converter's job to validate the converted object. If you're able to do the conversion successfully, then validation should be done separately using one of the ways described here.
For example, consider you're converting an string to a latitude. If the string can be parsed into a double, the converter should be happy. If you want to validate that the double is within the range [-90,+90], you should do so in a validator and not the converter.
Not mixing up different concerns will help a lot when you're handling exceptions, say in a #ControllerAdvice.

Redirect to error page in non controller class / request method..?

I am writing a Spring Boot application, in one of my controllers I have the following mapping:
#PostMapping("/")
public String doSomething( )
{
Foo bar = new Foo();
bar.doSomething();
return "/Complete";
}
And also another mapping for mappings in a Exception handling class:
#PostMapping("/error")
public ModelAndView doSomething( String error )
{
// Handle error here..
}
So in the class bar Which is a not a controller is there a way to redirect to the /error mapping passing down an error.. I ask this as it saves me have to constantly throw exceptions up call stacks / which catches all !
Thanks
First approach:
One if the solutions are to use the #ExceptionHandler annotation.
For example:
#PostMapping("/")
public String doSomething( )
{
Foo bar = new Foo();
bar.doSomething();
throw new RuntimeException("Hello Exception");
return "/Complete";
}
#ExceptionHandler(Exception.class)
public String doSomething( String error )
{
// Handle error here and return error view..
return "error_view";
}
In the example above each time an exception will be thrown from the controller scope which holds the #ExceptionHandler handler, this handler will be invoked, and using it you can decide which view will be rendered.
This solution is limited to one controller scope, the one that holds #ExceptionHandler handler.
Second approach:
Using a global handler exception resolver, you'll need to add a class that implements the HandlerExceptionResolver interface.
for example:
#Component
public class GlobalHandlerExceptionResolver implements HandlerExceptionResolver {
#Override
public ModelAndView resolveException(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception exception
) {
// return here your global exception view
return null;
}
}
It is important to mark that handler as a Spring Bean
Third approach:
To use controller advice, However, i believe that the second approach is more what you're looking for.

#ExceptionHandler for all controllers in spring-mvc

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

Categories

Resources