I am working in a spring mvc based project and have developed a ExceptionResolver by extending DefaultHandlerExceptionResolver to redirect to error page depending on the exception type. It is working for exception raised at facade, service, DAO layer.
But it doesn't work for any exceptions raised in Servlet filter. What changes should be made for that?
Below is my handlerExceptionResolver
public ModelAndView doResolveException(final HttpServletRequest request, final HttpServletResponse response, final Object obj,
final Exception exception){
ModelAndView modelAndView = super.doResolveException(request, response, obj, exception);
modelAndView = Objects.nonNull(modelAndView) ? modelAndView : new ModelAndView();
final String url = Config.getParameter(EXCEPTION_HANDLER_URL);
modelAndView.setViewName(url);
final FlashMap outputFlashMap = RequestContextUtils.getOutputFlashMap(request);
outputFlashMap.put(ERROR_DETAILS, exception);
if (exception instanceof BusinessExecutionException)
{
return handleBusinessExecutionExceptionMethod((BusinessExecutionException) exception, outputFlashMap, modelAndView);
}
else if (exception instanceof IntegrationExecutionException)
{
return handleIntegrationExecutionExceptionMethod((IntegrationExecutionException) exception, outputFlashMap,
modelAndView);
}
else if (exception instanceof DataAccessObjectExecutionException)
{
return handleDAOExecutionExceptionMethod((DataAccessObjectExecutionException) exception, outputFlashMap, modelAndView);
}
return handleMiscException(exception, outputFlashMap, modelAndView);
}
Use Spring exception handler:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
It is convenient - you can "catch" exception regarding type and HTTP status.
Related
I am currently trying to figure out the best way to handle errors in a spring boot app.
Assume that the app consists of thymeleaf templates served on paths starting with /admin, and a REST API served on other URIs.
My project currently has the following configuration:
Thymeleaf error templates located at /src/main/resources/templates/error/{id}.html, defined for errors 400, 401, 403, 404, 500.
Default thymeleaf error template /src/main/resources/templates/error.html
HttpSecurity configuration:
#Override
protected void configure(HttpSecurity http) throws Exception {
(...)
http
.anonymous();
http
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase()))
.accessDeniedHandler(new AccessDeniedHandlerImpl());
(...)
}
Exception Handlers for both rest and MVC:
#ControllerAdvice
public class ExceptionHandlers {
private final BasicErrorController basicErrorController;
public ExceptionHandlers(BasicErrorController basicErrorController) {
this.basicErrorController = basicErrorController;
}
#ExceptionHandler(Exception.class)
public Object handleAllExceptions(Exception e, HttpServletRequest request, HttpServletResponse response) {
return handle(e, request, response, HttpStatus.INTERNAL_SERVER_ERROR);
}
#ExceptionHandler(EntityNotFoundException.class)
public Object handleEntityNotFoundException(EntityNotFoundException e, HttpServletRequest request, HttpServletResponse response) {
return handle(e, request, response, HttpStatus.NOT_FOUND, I18nCodes.ENTITY_NOT_FOUND);
}
/**
* We exclude all exceptions deriving from {#link AccessDeniedException} from custom exception handling.
*/
#ExceptionHandler(AccessDeniedException.class)
public Object handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request, HttpServletResponse response) {
throw e;
}
/**
* We exclude all exceptions deriving from {#link AppBaseException} from custom exception handling.
*/
#ExceptionHandler(AppBaseException.class)
public Object handleAppBaseException(AppBaseException e, HttpServletRequest request, HttpServletResponse response) {
throw e;
}
private Object handle(Exception e, HttpServletRequest request, HttpServletResponse response, HttpStatus status) {
return handle(e, request, response, HttpStatus.INTERNAL_SERVER_ERROR, I18nCodes.getCodeByStatus(status));
}
private Object handle(Exception e, HttpServletRequest request, HttpServletResponse response, HttpStatus status, String message) {
String header = request.getHeader("Accept");
if (header != null && header.contains("text/html")) {
setErrorCode(request, response, status);
return basicErrorController.errorHtml(request, response);
}
return createJsonResponse(message, status, request.getRequestURI());
}
private ResponseEntity<ErrorResponseDTO> createJsonResponse(String message, HttpStatus status, String path) {
ErrorResponseDTO errorResponseDTO = new ErrorResponseDTO()
.setTimestamp(new Timestamp(System.currentTimeMillis()))
.setStatus(status.value())
.setMessage(message)
.setPath(path)
.setError(status.name().toLowerCase());
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return ResponseEntity.status(status).headers(httpHeaders).body(errorResponseDTO);
}
private void setErrorCode(HttpServletRequest request, HttpServletResponse response, HttpStatus httpStatus) {
request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, httpStatus.value());
response.setStatus(httpStatus.value());
}
}
Without the Exception handlers everything works about as expected, I get 401 error when I am not authenticated, and 403 when authenticated but lacking authorities. My custom exceptions are correctly mapped according to their message and Status defined in #ResponseStatus eg:
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class AccountInfoException extends AppBaseException {
protected AccountInfoException(String message) {
super(message);
}
protected AccountInfoException(String message, Throwable cause) {
super(message, cause);
}
public static AccountInfoException emailAlreadyExists() {
return new AccountInfoException(I18nCodes.EMAIL_EXIST);
}
public static AccountInfoException accountNotFound() {
return new AccountInfoException(I18nCodes.ACCOUNT_NOT_FOUND);
}
}
Will map to status 400 with one of two messages I18nCodes.EMAIL_EXIST or I18nCodes.ACCOUNT_NOT_FOUND.
I arrive on the correct thymeleaf error page depending on the status code - life is perfect.
The issue comes with the requirement of handling all other exceptions. It's not a possibility to let an unexpected error to the client. So for that I've defined an exception handler handing Exception, to return a generic 500 message. This breaks everything. I'm unable to access the Status codes of my custom exceptions
Authentication entry point and access denied handler are both ignored, and both cases of being not authenticated and having insufficient authority are handles in the same exception(AccessDeniedException). I also lose default mappings for some exceptions eg. org.springframework.security.authentication.LockedException returns status 401.
My temporary workaround was to handle all exceptions, and to create methods handling exceptions that I want to ignore and just rethrow the exception. I feel like the best thing I could do is remove all exception handlers and repack all container exceptions to my own custom exceptions, only issue is that there doesn't seem to be a way to do it.
I'm looking for a more permanent solution - what would be the best approach?
I have this spring service:
#Service
#Transactional
public class ConsorcioServiceImpl implements ConsorcioService {
...
#Autowired
private ConsorcioRepository consorcioRepository;
#Override
public void saveBank(Consorcio consorcio) throws BusinessException {
try {
consorcioRepository.save(consorcio);
}
catch(DataIntegrityViolationException divex) {
if(divex.getMessage().contains("uq_codigo")) {
throw new DuplicatedCodeException(divex);
}
else {
throw new BusinessException(dives);
}
}
catch (Exception e) {
throw new BusinessException(e);
}
}
}
That service uses this Spring Data repository:
#Repository
public interface ConsorcioRepository extendsCrudRepository<Consorcio, Integer> {
}
I'm calling the service from a spring controller:
#Controller
#RequestMapping(value = "/bank")
public class BancaController {
#Autowired
private ConsorcioService consorcioService;
#RequestMapping(value="create", method=RequestMethod.POST)
public ModelAndView crearBanca(#Valid BancaViewModel bancaViewModel, BindingResult bindingResult,
RedirectAttributes redirectAttributes) {
ModelAndView modelAndView;
MessageViewModel result;
try {
consorcioService.saveBank(bancaViewModel.buildBanca());
result = new MessageViewModel(MessageType.SUCESS);
redirectAttributes.addFlashAttribute("messageViewModel", result);
modelAndView = new ModelAndView("redirect:/banca/crear");
return modelAndView;
} catch (Exception e) {
result = new MessageViewModel(MessageType.ERROR);
modelAndView = new ModelAndView("crear-bancas");
modelAndView.addObject("messageViewModel", result);
return modelAndView;
}
}
But the exception I get in the controller is: org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
instead of the DuplicatedCodeException I throw in the service. I need to identify the type of exception so I can give a custom friendly user message.
also your DuplicatedCodeException , BusinessException should be runtime exception , or add for method saveBank :
#Transactinal(rolbackFor={BusinessException.class,DuplicatedCodeException.,class })
in other case spring will not rollback transaction.
from Spring documentation:
While the EJB default behavior is for the EJB container to
automatically roll back the transaction on a system exception (usually
a runtime exception), EJB CMT does not roll back the transaction
automatically on an application exception (that is, a checked
exception other than java.rmi.RemoteException). While the Spring
default behavior for declarative transaction management follows EJB
convention (roll back is automatic only on unchecked exceptions), it
is often useful to customize this.
Just add catch (TransactionSystemException tse) before catch (Exception e) branch and then extract your exception with getOriginalException().
try {
consorcioService.saveBank(bancaViewModel.buildBanca());
result = new MessageViewModel(MessageType.SUCESS);
redirectAttributes.addFlashAttribute("messageViewModel", result);
modelAndView = new ModelAndView("redirect:/banca/crear");
return modelAndView;
} catch (TransactionSystemException tse) {
final Throwable ex = tse.getOriginalException();
if (ex instanceof DuplicatedCodeException) {
// DuplicatedCodeException
}
} catch (Exception e) {
result = new MessageViewModel(MessageType.ERROR);
modelAndView = new ModelAndView("crear-bancas");
modelAndView.addObject("messageViewModel", result);
return modelAndView;
}
That happens because your exception is wrapped in RollbackException as Throwable cause. In it's turn RollbackException is also a cause of TransactionSystemException.
You can build global exception handler to catch and customize all exceptions as you wish:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT)
#ExceptionHandler(TransactionSystemException.class)
public ModelAndView handleDupilatedCode(HttpServletRequest req, TransactionSystemException ex) {
// Build you exception body here
Throwable e = ex.getOriginalException();
if(e instanceof DuplicatedCodeException)
// throw
// Or build custom exception as you want
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName("error");
return mav;
}
}
#ControllerAdvice is available since Spring 3.2
Also you can use #ExceptionHandler at the #Controller level, or create this handling in abstract-controller if you have one as a super class of all your controllers.
public class FooController {
//...
#ExceptionHandler({ CustomException1.class, CustomException2.class })
public void handleException() {
//
}
}
There are some other approaches, for full reference please follow the:
Spring IO: Exception handling in Spring MVC
Baeldung: Exception handling with Spring
Ours is a Spring MVC based REST application. I am trying to use ExceptionHandler annotation to handle all errors and exceptions.
I have
#ExceptionHandler(Throwable.class)
public #ResponseBody String handleErrors() {
return "error";
}
This works whenever there is an exception thrown and it doesn't work for any errors.
I am using Spring 4.0. Is there any work-around?
Contrary to what the ExceptionHandler#value() attribute indicates
Class<? extends Throwable>[] value() default {};
and #ExceptionHandler is only meant to handle Exception and its sub types.
Spring uses ExceptionHandlerExceptionResolver to resolve your annotated handlers, using the following method
doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod, Exception exception)
which as you can see only accepts an Exception.
You cannot handle Throwable or Error types with #ExceptionHandler with this configuration.
I would tell you to provide your own HandlerExceptionResolver implementation which does handle Throwable instances, but you'd need to provide your own DispatcherServlet (and most of the MVC stack) yourself since DispatcherServlet does not catch Throwable instances at any place where you could make any significant difference.
Update:
Since 4.3, Spring MVC wraps a thrown Throwable value in a NestedServletException instance and exposes that to the ExceptionHandlerExceptionResolver.
You can do a kind of Hacking to capture Error in Spring MVC.
First, define an Interceptor like this :
public class ErrorHandlingInterceptor extends HandlerInterceptorAdapter {
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception
{
super.afterCompletion(request, response, handler, ex);
controller.handleError(ex.getCause(), request, response);
} }
Second, define a method in your controller like "handleError" method:
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setExceptionId(exceptionId);
errorResponse.setErrorMsg(ex.toString());
errorResponse.setServerStackTrace(serverStackTrace(ex));
response.setStatus(responseCode);
response.setContentType("application/json");
ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
writer.writeValue(response.getOutputStream(), errorResponse);
Finally, config your interceptor in Spring configuration.
<mvc:interceptors>
<bean class="ErrorHandlingInterceptor" />
</mvc:interceptors>
Code in DispatchServlet:
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// This is where to handle Exception by Spring.
// If Error happens, it will go to catch Error statement
// which will call afterCompletion method
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
// Trigger after-completion for successful outcome.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
}
catch (Exception ex) {
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
catch (Error err) {
ServletException ex = new NestedServletException("Handler processing failed", err);
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
Ours is a Spring MVC based REST application. I am trying to use ExceptionHandler annotation to handle all errors and exceptions.
I have
#ExceptionHandler(Throwable.class)
public #ResponseBody String handleErrors() {
return "error";
}
This works whenever there is an exception thrown and it doesn't work for any errors.
I am using Spring 4.0. Is there any work-around?
Contrary to what the ExceptionHandler#value() attribute indicates
Class<? extends Throwable>[] value() default {};
and #ExceptionHandler is only meant to handle Exception and its sub types.
Spring uses ExceptionHandlerExceptionResolver to resolve your annotated handlers, using the following method
doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod, Exception exception)
which as you can see only accepts an Exception.
You cannot handle Throwable or Error types with #ExceptionHandler with this configuration.
I would tell you to provide your own HandlerExceptionResolver implementation which does handle Throwable instances, but you'd need to provide your own DispatcherServlet (and most of the MVC stack) yourself since DispatcherServlet does not catch Throwable instances at any place where you could make any significant difference.
Update:
Since 4.3, Spring MVC wraps a thrown Throwable value in a NestedServletException instance and exposes that to the ExceptionHandlerExceptionResolver.
You can do a kind of Hacking to capture Error in Spring MVC.
First, define an Interceptor like this :
public class ErrorHandlingInterceptor extends HandlerInterceptorAdapter {
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception
{
super.afterCompletion(request, response, handler, ex);
controller.handleError(ex.getCause(), request, response);
} }
Second, define a method in your controller like "handleError" method:
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setExceptionId(exceptionId);
errorResponse.setErrorMsg(ex.toString());
errorResponse.setServerStackTrace(serverStackTrace(ex));
response.setStatus(responseCode);
response.setContentType("application/json");
ObjectWriter writer = mapper.writer().withDefaultPrettyPrinter();
writer.writeValue(response.getOutputStream(), errorResponse);
Finally, config your interceptor in Spring configuration.
<mvc:interceptors>
<bean class="ErrorHandlingInterceptor" />
</mvc:interceptors>
Code in DispatchServlet:
catch (Exception ex) {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
// This is where to handle Exception by Spring.
// If Error happens, it will go to catch Error statement
// which will call afterCompletion method
mv = processHandlerException(processedRequest, response, handler, ex);
errorView = (mv != null);
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
render(mv, processedRequest, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
"': assuming HandlerAdapter completed request handling");
}
}
// Trigger after-completion for successful outcome.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
}
catch (Exception ex) {
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
catch (Error err) {
ServletException ex = new NestedServletException("Handler processing failed", err);
// Trigger after-completion for thrown exception.
triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
throw ex;
}
I have a Spring MVC controller throw two kind of exception:
#RequestMapping(value = "forwardRefundApply",method = RequestMethod.GET)
public ModelAndView forwardRefundApply1(String ticketNbr)throws Exception {
if(true)
throw new Exception();
else
throw new ApplicationException("test");
}
then I write a AOP class to hanlder the Exception then return Model like this:
#Pointcut("execution(public * ..*(..))")
public void getRefundPointCut() {
}
#AfterThrowing(pointcut="getRefundPointCut()", throwing="e")
public ModelAndView throwException(Exception e){
ModelAndView mav = null;
if(e instanceof ApplicationException)
{
e.printStackTrace();
mav = new ModelAndView(CommonConstants.ERRORPAGE);
mav.addObject("errorMsg", "application error");
return mav;
}
else{
e.printStackTrace();
mav = new ModelAndView(CommonConstants.ERRORPAGE);
mav.addObject("errorMsg", "system error");
return mav;
}
}
the aop is work . but the the result is error. system error:
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.NoSuchMethodError
is Aspect class cannot return ModelAndView to Controller?
Why use AOP in this case at all? Spring comes with everything you need:
HandlerExceptionResolver is a global entry point into exception handling.
#ExceptionHandler lets you define handlers in controllers.
#ControllerAdvice lets you define #ExceptionHandlers on a global level.
The NoSuchMethodError is unrelated to your question and is caused by something you haven't shown us.
As for the question
is Aspect class cannot return ModelAndView to Controller?
I couldn't find any reference to it anywhere in Spring's AOP documentation, but you can see it in the implementation.
When you declare a #AfterThrowing advice, Spring uses a AspectJAfterThrowingAdvice to handle it. Its invoke(..) method is implemented as
#Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
catch (Throwable t) {
if (shouldInvokeOnThrowing(t)) {
invokeAdviceMethod(getJoinPointMatch(), null, t);
}
throw t;
}
}
where mi.proceed() invokes your advised method and invokeAdviceMethod(..) invokes your #AfterThrowing advice method. Notice that it does nothing with the return value. As such, you can return a ModelAndView object from a #AfterThrowing advice method, but it won't serve any purpose, it'll simply be discarded.
A possible alternative is to declare a #Around advice. Within it, you wrap the proceeding call and catch the possible exceptions, handling them appropriately
#Around(value = "getRefundPointCut()")
public ModelAndView throwException(ProceedingJoinPoint joinPoint) throws Throwable {
ModelAndView mav = null;
try {
return (ModelAndView) joinPoint.proceed(); // might want to make sure that it is a ModelAndView
} catch(ApplicationException e) {
e.printStackTrace();
mav = new ModelAndView("home");
mav.addObject("errorMsg", "application error");
return mav;
} catch (Exception e) {
e.printStackTrace();
mav = new ModelAndView("home");
mav.addObject("errorMsg", "system error");
return mav;
}
}
Here you return the value of the advised method if it returns correctly. Or your catch any thrown Exception and again handle it appropriately by returning a different ModelAndView.
I haven't tested it myself but Spring AOP works with proxy objects and not the actual objects. So e is actually an instance of proxy and not of ApplicationException. So the following condition never executes to true.
if(e instanceof ApplicationException)
Easiest way of handling this would be to mark your aop setting in Spring configuration file with proxy-target-class="true".
<aop:aspectj-autoproxy proxy-target-class="true"/>
HTH