#ControllerAdvice method is not getting executed and it executes Base Response Class - java

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.

Related

How to use #ControllerAdvice for catching exceptions from Service classes?

I have some Service classes which contain multiple methods that throws error, an example of methods that throws an error:
public Optional<Item> getItemById(Long itemId) throws Exception {
return Optional.of(itemRepository.findById(itemId).
orElseThrow(() -> new Exception("Item with that id doesn't exist")));
}
Should I catch errors in the #ControllerAdvice annoted class?
How should I do it?
The controller marked with #ControllerAdvice will intercept any exception thrown in the stack called when a request arrives. If the question is if you should catch errors with ControllerAdvice, is up to you, but it allows you to customize the behaviour once a exception is thrown. To do it you should create a class like this:
#ControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler({ Exception.class, MyCustomException.class }) //Which exceptions should this method intercept
public final ResponseEntity<ApiError> handleException(Exception ex){
return new ResponseEntity<>(body, HttpStatus.NOT_FOUND); //Or any HTTP error you want to return
}
}

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.

Spring boot REST validation error response

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.

Spring REST #ResponseStatus with Custom exception class does not change the return Status code

I have a exception class like follows
#ResponseStatus(value=HttpStatus.UNPROCESSABLE_ENTITY, reason="Unprocessable Entity") // 422
public class UnprocessableEntityException extends RuntimeException {
}
Now the status is not returned as 422 unless I write a specific handler in the Controller class like :
#ExceptionHandler(UnprocessableEntityException.class)
#ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
public String handleException(Exception ex) {
...
}
As I understand I should not need #ExceptionHandler in first place, not sure what am I missing.
Throwing a #ResponseStatus annotated exception from a controller method should be enough for the framework to write the HTTP status code - no #ExceptionHandler necessary.
The following will write a 422 Status on hitting the webapp root as expected:
#Controller
public class ExceptionController {
#RequestMapping("/")
public void action() {
throw new ActionException();
}
#ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY, reason = "nope")
public static class ActionException extends RuntimeException {}
}
This works courtesy of the ResponseStatusExceptionResolver which is created by Spring MVC by default - if it's not working for you, my guess is that this default exception resolver has been removed (by e.g. overriding WebMvcConfigurationSupport.configureHandlerExceptionResolvers or otherwise configuring your context's HandlerExceptionResolvers so that the ResponseStatusExceptionResolver is trumped.)
The exception thrown should not be handled by code or by other exception resolvers, for example it shouldn't be handled by #ExceptionHandler, because that will override the status code specified by the exception class's #ResponseStatus.

Why #ExceptionHandler(MethodArgumentNotValidException.class) is ignored in favour of #ExceptionHandler(Exception.class)

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.

Categories

Resources