How to get all exception handlers annotated by #ExceptionHanlder and I can call them manually?
Background
I need to handle some exceptions by my own exception handlers but in some situation my handled exceptions are not thrown directly by spring, and they are wrapped in the cause by. So I need to handle these caused by exceptions in one place using my own exception handling strategy in the existing #ExceptionHandlers. How can I do that?
Try to use Java Reflection Api to find classes annotated with "ExceptionHanlder". And invoke any method or whatever you want.
You can extend ResponseEntityExceptionHandler and make it a #ControllerAdvise like below.
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({YourException.class})
public ResponseEntity<Object> handleMyException(Exception ex, WebRequest request) {
... handle the way you like it
return new ResponseEntity<Object>(YourErrorObject, new HttpHeaders(), HttpStatus);
}
}
Spring provides #ControllerAdvice annotation that we can use with any class to define our global exception handler. The handler methods in Global Controller Advice is same as Controller based exception handler methods and used when controller class is not able to handle the exception.
You want to use exception handling strategy in your one place. that you can define multiple exception or make message using exception in exception controller.
like this :
#ExceptionHandler(value = { HttpClientErrorException.class, HTTPException.class, HttpMediaTypeException.class,
HttpMediaTypeNotSupportedException.class, HttpMessageNotReadableException.class })
or
#ExceptionHandler
#ResponseBody
ExceptionRepresentation handle(Exception exception) {
ExceptionRepresentation body = new ExceptionRepresentation(exception.getLocalizedMessage());
HttpStatus responseStatus = resolveAnnotatedResponseStatus(exception);
return new ResponseEntity<ExceptionRepresentation>(body, responseStatus);
}
HttpStatus resolveAnnotatedResponseStatus(Exception exception) {
ResponseStatus annotation = findMergedAnnotation(exception.getClass(), ResponseStatus.class);
if (annotation != null) {
return annotation.value();
}
return HttpStatus.INTERNAL_SERVER_ERROR;
}
Here is a work around. You can catch the the wrapping exception and then check the root cause of the exception. Here is an example of MySQLIntegrityConstraintViolationException which is wrapped by DataIntegrityViolationException in spring:
#ExceptionHandler(DataIntegrityViolationException.class)
#ResponseBody
public ResponseEntity<Object> proccessMySQLIntegrityConstraint(DataIntegrityViolationException exception) {
if (exception.getRootCause() instanceof MySQLIntegrityConstraintViolationException) {
doSomething....
} else {
doSomethingElse...
}
}
Related
I would like to handle my exceptions correctly in Spring, so I have a question about exceptionHandler syntax : Is it ok to throw specific exceptions in the controller, if they are caught by the exception handler ?
More specifically :
Here is the Exception :
public class UnknownUserException extends Exception {
private static final long serialVersionUID = 1L;
public UnknownUserException(String message) {
super(message);
}
}
Here is the ExceptionHandler with the specific method for UnknownUserException :
#ControllerAdvice
#ResponseBody
public class ControllerExceptionHandler {
#ExceptionHandler(UnknownUserException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public ErrorMessage unknownUserExceptionMessage(UnknownUserException ex, WebRequest request) {
ErrorMessage message = new ErrorMessage("The user doesn't exist: " +ex.getLocalizedMessage(), ex);
return message;
}
}
Here is one example of a mapping that may raise that exception :
#GetMapping({"/user/{id}"})
public ResponseEntity<UserProfileDto> getById(#PathVariable Long id) throws UnknownUserException {
UserProfileDto user = userService.findById(id);
return ResponseEntity.ok(user);
}
The userService.findById(id) may throw the UnknownUserException.
From what I understood, the controllerAdvice "overrides" the controller in case of that specific exception thrown by a service, but then, what should I do with my controller ? should I throw the exception again (like above) or catch the specific exception and return a ResponseEntity(HttpStatus.NOT_FOUND) ?
In an ideal scenario exception should be thrown immediately when it is known in your case as you mentioned service method will throw UnknownUserException that the right thing to do. Your Controller Advice should be able to handle that exception. ContollerAdvice will handle any matching exception that is thrown during execution of the request irrespective of the origin of exception.
Refer to this link for other options on handling exception
I have a task to handle all exceptions in my project via #ControllerAdvice. The project has multiple controller methods. I have a custom exception which is thrown by some of these controller methods.
For handling this exception, I have a class annotated with #ControllerAdvice and I have written some #ExceptionHandler methods.
#EnableWebMvc
#ControllerAdvice
public class ExceptionHandler {
#ExceptionHandler(WebServiceContactException.class)
#ResponseStatus(value = HttpStatus.PRECONDITION_FAILED)
#ResponseBody
public MyResponse dealWebServiceContactExceptionForAddElement(WebServiceContactException e, AddRequest request) {
int errorCode = HttpStatus.PRECONDITION_FAILED.value();
LOGGER.error("Failed to contact service for " + request.getFunctionalId(), e);
auditTrail.reportFailureInfo(e.getMessage());
return MyResponse.Builder.newBuilder(errorCode)
.setDescription(Constants.EXECUTION_FAILED)
.build();
}
#ExceptionHandler(WebServiceContactException.class)
#ResponseStatus(value = HttpStatus.PRECONDITION_FAILED)
#ResponseBody
public MyResponse dealWebServiceContactExceptionForDeleteElement(WebServiceContactException e, DeleteRequest request) {
int errorCode = HttpStatus.PRECONDITION_FAILED.value();
LOGGER.error("Failed to contact service for " + request.getFunctionalId(), e);
auditTrail.reportFailureInfo(e.getMessage());
return MyResponse.Builder.newBuilder(errorCode)
.setDescription(Constants.EXECUTION_FAILED)
.build();
}
}
Doing this throws an IllegalStateException
Caused by: java.lang.IllegalStateException: Ambiguous #ExceptionHandler method mapped for [class com.action.resource.WebServiceContactException]
Is there anyway I can define multiple #ExceptionHandler methods dealing with same exception WebServiceContactException but with different web requests?
PS : I know I can write #ExceptionHandler method without giving request object in argument but I really need that object to populate certain fields in my logs for monitoring purposes.
I want to implement custom exception handler for status NotFoundException for Spring Boot:
#ExceptionHandler({ AccessDeniedException.class, NotFoundException.class })
public ResponseEntity<ErrorResponseDTO> accessDeniedExceptionHandler(final AccessDeniedException ex) {
......
}
I can't find what is the proper import for NotFoundException Do you know what exception what is the proper import for that case?
Either add an exception handler for a NoHandlerFoundException:
#ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<ErrorResponseDto> handle(NoHandlerFoundException e) {
// ...
}
Or have your controller advice extend ResponseEntityExceptionHandler and override the handleNoHandlerFoundException method.
By the way, your code snippet declares a handler for two different exceptions while the method parameter final AccessDeniedException ex explicitly expects an exception of type AccessDeniedException. I would suggest either declaring multiple handler methods or generalize the paramater to an Exception instead.
I agree with #Michiel on, method parameter(AccessDeniedException ex) should be parent class of below classes:
AccessDeniedException
NotFoundException
try this
#ExceptionHandler({ AccessDeniedException.class, NotFoundException.class })
public ResponseEntity<ErrorResponseDTO> accessDeniedExceptionHandler(final **Exception** ex) {
......
}
i have used #ControllerAdvice like
#ControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler({BadRequestException.class, IllegalArgumentException.class, MaxUploadSizeExceededException.class})
#ResponseBody
public ResponseEntity<ErrorResponse> handleBadRequestException(Exception exception, WebRequest request) {
String message = StringUtils.isEmpty(exception.getMessage()) ? properties.getGeneralMessages().get("fail") : exception.getMessage();
if (message.contains(";"))
message = message.substring(0, message.indexOf(";"));
return getResponseEntity(message, null);
}
}
I want to let HandlerExceptionResolver resolve any Exceptions that I don't explicit catch via #ExceptionHandler annotation.
Anyways, I want to apply specific logic on those exceptions. Eg send a mail notification or log additionally. I can achieve this by adding a #ExceptionHandler(Exception.class) catch as follows:
#RestControllerAdvice
public MyExceptionHandler {
#ExceptionHandler(IOException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
public Object io(HttpServletRequest req, Exception e) {
return ...
}
#ExceptionHandler(Exception.class)
public Object exception(HttpServletRequest req, Exception e) {
MailService.send();
Logger.logInSpecificWay();
//TODO how to continue in the "normal" spring way with HandlerExceptionResolver?
}
}
Problem: if I add #ExceptionHandler(Exception.class) like that, I can catch those unhandled exceptions.
BUT I cannot let spring continue the normal workflow with HandlerExceptionResolver to create the response ModelAndView and set a HTTP STATUS code automatically.
Eg if someone tries a POST on a GET method, spring by default would return a 405 Method not allowed. But with an #ExceptionHandler(Exception.class) I would swallow this standard handling of spring...
So how can I keep the default HandlerExceptionResolver, but still apply my custom logic?
To provide a complete solution: it works just by extending ResponseEntityExceptionHandler, as that handles all the spring-mvc errors.
And the ones not handled can then be caught using #ExceptionHandler(Exception.class).
#RestControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> exception(Exception ex) {
MailService.send();
Logger.logInSpecificWay();
return ... custom exception
}
}
Well, I was facing the same problem some time back and have tried several ways like extending ResponseEntityExceptionHandler but all them were solving some problems but creating other ones.
Then I have decided to go with a custom solution which was also allowing me to send additional information and I have written below code
#RestControllerAdvice
public class MyExceptionHandler {
private final Logger log = LoggerFactory.getLogger(this.getClass());
#ExceptionHandler(NumberFormatException.class)
public ResponseEntity<Object> handleNumberFormatException(NumberFormatException ex) {
return new ResponseEntity<>(getBody(BAD_REQUEST, ex, "Please enter a valid value"), new HttpHeaders(), BAD_REQUEST);
}
#ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<Object> handleIllegalArgumentException(IllegalArgumentException ex) {
return new ResponseEntity<>(getBody(BAD_REQUEST, ex, ex.getMessage()), new HttpHeaders(), BAD_REQUEST);
}
#ExceptionHandler(AccessDeniedException.class)
public ResponseEntity<Object> handleAccessDeniedException(AccessDeniedException ex) {
return new ResponseEntity<>(getBody(FORBIDDEN, ex, ex.getMessage()), new HttpHeaders(), FORBIDDEN);
}
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> exception(Exception ex) {
return new ResponseEntity<>(getBody(INTERNAL_SERVER_ERROR, ex, "Something Went Wrong"), new HttpHeaders(), INTERNAL_SERVER_ERROR);
}
public Map<String, Object> getBody(HttpStatus status, Exception ex, String message) {
log.error(message, ex);
Map<String, Object> body = new LinkedHashMap<>();
body.put("message", message);
body.put("timestamp", new Date());
body.put("status", status.value());
body.put("error", status.getReasonPhrase());
body.put("exception", ex.toString());
Throwable cause = ex.getCause();
if (cause != null) {
body.put("exceptionCause", ex.getCause().toString());
}
return body;
}
}
Create classes for exception handling in this way
#RestControllerAdvice
public class MyExceptionHandler extends BaseExceptionHandler {
}
public class BaseExceptionHandler extends ResponseEntityExceptionHandler {
}
Here ResponseEntityExceptionHandler is provided by spring and override the several exception handler methods provided by it related to the requestMethodNotSupported,missingPathVariable,noHandlerFound,typeMismatch,asyncRequestTimeouts ....... with your own exception messages or error response objects and status codes
and have a method with #ExceptionHandler(Exception.class) in MyExceptionHandler where the thrown exception comes finally if it doesn't have a matching handler.
I had the same issue and solved it creating a implementation of the interface HandlerExceptionResolver and removing the generic #ExceptionHandler(Exception.class) from the generic handler method.
.
It works this way:
Spring will try to handle the exception calling MyExceptionHandler first, but it will fail to find a handler because the annotation was removed from the generic handler. Next it will try other implementations of the interface HandlerExceptionResolver. It will enter this generic implementation that just delegates to the original generic error handler.
After that, I need to convert the ResponseEntity response to ModelAndView using MappingJackson2JsonView because this interface expects a ModelAndView as return type.
#Component
class GenericErrorHandler(
private val errorHandler: MyExceptionHandler,
private val objectMapper: ObjectMapper
) : HandlerExceptionResolver {
override fun resolveException(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception): ModelAndView? {
// handle exception
val responseEntity = errorHandler.handleUnexpectedException(ex)
// prepare JSON view
val jsonView = MappingJackson2JsonView(objectMapper)
jsonView.setExtractValueFromSingleKeyModel(true) // prevents creating the body key in the response json
// prepare ModelAndView
val mv = ModelAndView(jsonView, mapOf("body" to responseEntity.body))
mv.status = responseEntity.statusCode
mv.view = jsonView
return mv
}
}
I wrote a custom exception with Spring 5 reactive
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public class AddressNotFoundException extends RuntimeException{
public AddressNotFoundException(String message) {
super(message);
}
and I call this one in a service:
#Override
public Mono<Address> getById(String id) {
Address addressFound=repository.findById(id).block();
if(Objects.equals(addressFound, null))
throw new AddressNotFoundException("Address #"+id+" not found");
return Mono.just
(addressFound);
}
but when I reach this page an exception is thrown but it's not a 404 but a null pointer exception and a error 500 page but with the correct message ?
The AddressNotFound is never thrown, only the Nullpointer exception but with my custom message ???
Can you help me please ?
Here is my controller :
#GetMapping("/address/{id}")
public Mono<Address> byId(#PathVariable String id) {
return addressService.getById(id);
}
Thanks
if your address is null,
repository.findById(id).block();
Should be throwing the NullPointerException I suppose. In that way it could never reach the code line to throw the custom exception.
Instead of extending RuntimeException try extending just generic Exception.
Spring offers a ControllerAdvice annotation to intercept exceptions that are thrown
#ControllerAdvice
public class ExceptionController extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = { AddressNotFoundException.class })
protected ResponseEntity<Object> handleAddressNotFoundException(Exception ex, WebRequest request) {
AddressNotFoundException notFound = (AddressNotFoundException)ex;
return handleExceptionInternal(ex, String.valueOf(notFound), new HttpHeaders(), HttpStatus.NOT_FOUND, request);
}
}
This will send the error back to the client as a 404 in which you can digest on the client side to display to the user typically in the form of a json string. Can either override the exceptions toString method to return it as a json or write a helper method that would do that.
You probably have a HandlerExceptionResolver bean which is causing 500 for some reason. Please try taking that off for a while.
I've tried with spring boot 1.5 it works with Spring Boot 2 without webflux it works, so it seems that Webflux can't handle custom exception ???