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.
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.
I have a Spring MVC application where most of the controllers receive the HTTP parameters encapsulated in DTOs, nothing special:
#RequestMapping(...)
public String handleUpdate(#Valid MyDto myDto, BindingResult bindingResult){...}
So the input Http parameters must have values that can be converted to the fields of my DTOs, when this doesn't happen and an binding exception is thrown Spring adds an Error with the exception message to the binding result. For example if myDto has an enum field but the Https param for that field has an invalid value.
The problem is these error messages get back to the UI and they can reveal information about the technologies, frameworks, libraries we use to a possible attacker. I need to hide these technical details behind a generic error message, say "invalid input".
I can add a BindingErrorProcessors to each controller's data binder like:
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setBindingErrorProcessor(new MyBindingErrorProcessor());
}
but of course I don't what to do this for each controller class.
Question How can I set the BindingErrorProcessor for all my controllers? I think #ControllerAdvice only works for unhandled exceptions, maybe there's something similar?
Just for others looking this up, #ControllerAdvice accepts #InitBinder methods:
#ControllerAdvice
public class MyAdvice {
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.setBindingErrorProcessor(new BindingErrorProcessor() {
#Override
public void processMissingFieldError(String missingField, BindingResult bindingResult) {
log.info("missing field " + missingField);
}
#Override
public void processPropertyAccessException(PropertyAccessException ex, BindingResult bindingResult) {
bindingResult.rejectValue(ex.getPropertyName(), "label.input.error", "label.general.error");
log.info("property access exc. " + ex);
}
}
}
Method processPropertyAccessException will be called for conversion exceptions without adding any new Error to the bindingResult. You will have to add the error yourself.
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.
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
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.