Spring MVCException handling - java

I have a MVC controller having at least 50 functions in there and my call to the services are not wrapped around try catch and some of the exceptions are getting eaten. I am trying to find the best way to handle this.
Shall I wrap the calls around try catch in individual function or is there any function I can use that can log the exception. I dont want to send alternate view or something just simply want to record in the logs.

You should probably look at #ExceptionHandler (controller-based exception Handling) or #ControllerAdvice (global exception handling). This article explains both in detail.
The other possible solution is AOP.

You could extend SimpleMappingExceptionResolver and override logException() like this:
public class CustomExceptionResolver extends SimpleMappingExceptionResolver {
#Override
protected void logException(Exception ex, HttpServletRequest request) {
if (ex != null) {
logger.error(ex);
}
}
}
However, if the exception occurs in the view, the above will not log it. For this, you can extend HandlerInterceptorAdapter:
public class ViewExceptionLoggerInterceptor extends HandlerInterceptorAdapter {
protected final Log logger = LogFactory.getLog(getClass());
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
if (ex != null) {
logger.error(ex);
}
}
}

To centralize exception handling, use the follow:
#ControllerAdvice
public class MyExceptionHandler{
#ExceptionHandler(SomeException.class)
public final ResponseEntity<ReturnValue> handleSomeException(SomeException ex) {
// exception handling
}
}
If you need ModelAndView as a return value, just change the return type to it, and build the proper object in the body.

Related

Call global #ControllerAdvice method from controller #ExceptionHandler method

Is it possible to handle exceptions in a controller using #ExceptionHandler and then rethrow that exception so a #ControllerAdvice can handle it?
I am trying to do it but when I rethrow the exception it doesn't reach the ControllerAdvice.
#RestController
public class MyController {
#GetMapping
public Something getSomething() {
...
}
#ExceptionHandler(Exception.class)
public void handleException(Exception e) throws Exception {
System.out.println("Log!");
throw e;
}
#RestControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(Exception.class)
public ResponseEntity<...> handleException(Exception exception) {
// Not working
...
}
Some more context (hopefully you can give me some different ideas on implementing this):
Today our application has a GlobalExceptionHandler (annotated with RestControllerAdvice) that handles exceptions.
Now, we have a new business demand that 2 particular endpoints we have must now log some additional info like "endpoint GET /something got this error".
Any kind of exception must be logged, I need to log which particular endpoint it happened and I need to keep the global handler in place as it is responsible for creating the error responses.
One way to solve this would be to change each method on the global handler to inject the current request, retrieve the url from it, check a particular configuration if I need to log for that endpoint and log it, but it feels wrong adding this behaviour to all handler methods.
Can you use the aspect of AOP and wrap your request with catch an any exceptions?
#Aspect
#Component
public class RequestWrapperAspect {
#Around("(#annotation(org.springframework.web.bind.annotation.RequestMapping) || " +
"#annotation(org.springframework.web.bind.annotation.PostMapping) || " +
"#annotation(org.springframework.web.bind.annotation.PutMapping) || " +
"#annotation(org.springframework.web.bind.annotation.DeleteMapping) || " +
"#annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"#annotation(org.springframework.web.bind.annotation.PatchMapping)) && execution(public * *(..))")
public Object wrapRequest(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try {
return proceedingJoinPoint.proceed();
} catch (Exception e) {
//whatever you need to do
throw e;
}
}

Can I determine in a servlet filter whether a HttpServletRequest maps to a particular Spring controller class

I'm working on an application that's using a OncePerRequestFilter to do some custom log-like behavior using the incoming web request. This behavior uses both the HttpServletRequest & HttpServletResponse. Additionally, the filter uses both ContentCachingRequestWrapper & ContentCachingResponseWrapper to access the request/response bodies.
It's been decided that we only want to do this behavior when methods in particular Spring controllers have been called, since it's not something we want to do for other controllers/actuator endpoints/etc. Is there a way to tell whether the incoming request will be (or was) mapped to a controller?
public class ExampleFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// Can I tell here whether this will be mapping to an endpoint in
// ExampleController or NestedExampleController?
ContentCachingRequestWrapper requestToUse = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseToUse = new ContentCachingResponseWrapper(response);
try {
filterChain.doFilter(requestToUse, responseToUse);
// Can I tell here whether this was mapped to an endpoint in
// ExampleController or OtherExampleController?
} finally {
responseToUse.copyBodyToResponse(); // Write the cached body back to the real response
}
}
}
#RestController
#RequestMapping("/example")
public class ExampleController {
#GetMapping("/{id}")
public Example retrieveExample() {
return getValue(); // Retrieve the value
}
// ...
}
#RestController
#RequestMapping("/example/{id}/nested")
public class NestedExampleController {
#GetMapping("/{nestedId}")
public NestedExample retrieveNestedExample() {
return getValue(); // Retrieve the value
}
// ...
}
I've dug around the Spring MVC/Boot internals a bit, and I'm not sure if there's a way to easily do this. As an alternative, I can do some manual URL pattern matching, which probably won't necessarily exactly match up to the methods in the controllers, but may get me close enough to be an acceptable solution.
To summarize: is there a way in a web filter to tell whether the incoming request will be mapped to a controller (prior to executing the filter chain) or whether it was mapped to a controller (after executing the filter chain)?
What you want is basically a cross-cutting concern that targets a specific part of your application - in this case, logging.
This is one of the most common use-cases for aspect-oriented programming, for which Spring has built-in support using AspectJ-style pointcuts.
You will need:
To enable AOP within your Spring configuration on a configuration class, as follows:
#Configuration
#EnableAspectJAutoProxy
public class AopConfiguration {
}
Define an aspect, e.g. as follows:
#Aspect
public class LoggingAspect {
Logger log = ...; // define logger
// Matches all executions in com.example.ExampleController,
// with any return value, using any parameters
#Pointcut("execution(* com.example.ExampleController.*(..))")
public void controllerExecutionPointcut() {}
#Around("controllerExecutionPointcut()")
public Object aroundTargetControllerInvocation(ProceedingJoinPoint pjp) {
log.debug("About to invoke method: {}", pjp.getSignature().getName());
try {
return pjp.proceed();
} catch (Throwable t) {
// note that getArgs() returns an Object[],
// so you may want to map it to a more readable format
log.debug("Encountered exception while invoking method with args {}", pjp.getArgs());
throw t;
}
log.debug("Sucessfully finished invocation");
}
}
See e.g. this guide to learn more about pointcut expressions.
Another common use-case for this is timing your method calls, although for that something like Micrometer (and the Micrometer adapter for Spring) using #Timed would probably be better.
You may also wish to read through the reference documentation, which devotes quite a lot of information on how AOP in Spring works.
Note: as will almost all other Spring proxying mechanisms, invocations from within the target object will not be proxied, i.e. this.otherControllerMethod() will not be subject to interception by the above advice. Similarly, private methods also cannot be intercepted. See section 5.4.3 of the reference documentation for more information.
As a last note, if performance is of great importance, you should check out AspectJ compile-time or load-time weaving, which gets rid of some of the overhead introduced by Spring's proxying mechanism (which is what Spring AOP uses under the hood). This will most likely not be necessary in your case, but is good to keep in mind.
Edit for comment:
Thanks! One caveat with this approach is that it does not give me access to the HttpServletRequest or HttpServletResponse, which is something I'm making use of. I can see where this would be helpful if that wasn't something I needed. I see that I wasn't explicit about that requirement in my question, so I'll update accordingly.
Indeed, that is unfortunately not directly possible with this approach. If you really need the request, then the HandlerInterceptor approach mentioned by #DarrenForsythe is another possible to go. If all you're going for is logging though, I see no reason why you absolutely need the request - unless you wish to extract specific headers and log those.
In that case, IMO, a OncePerRequestFilter as you originally tried would be far better, as you can control for which requests the filter gets applied (using shouldNotFilter(HttpServletRequest request) and matching on the URL).
After some additional poking around and some trial and error, I discovered that the controller is accessible through the RequestMappingHandlerMapping bean. When the request can be handled by a controller, this will map the request to a HandlerMethod for the controller's request handling method.
public class ExampleFilter extends OncePerRequestFilter {
private RequestMappingHandlerMapping requestMappingHandlerMapping;
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
Object handler = getHandlerBean(request);
boolean isHandledController = handler instanceof ExampleController
|| handler instanceof NestedEampleController;
if (!isHandledController) {
filterChain.doFilter(request, response);
return;
}
// ...
}
private Object getHandlerBean(HttpServletRequest request) {
try {
HandlerExecutionChain handlerChain = requestMappingHandlerMapping.getHandler(request);
if (handlerChain != null) {
Object handler = handlerChain.getHandler();
if (handler instanceof HandlerMethod) {
return ((HandlerMethod) handler).getBean();
}
}
return null;
} catch (Exception e) {
return null;
}
}
#Override
protected void initFilterBean() {
WebApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
requestMappingHandlerMapping = appContext.getBean(RequestMappingHandlerMapping.class);
}
}
To be extra thorough and truly mimic Spring's handler logic, the DispatcherServlet logic could be used/mimicked instead of directly referencing RequestMappingHandlerMapping. This will consult all handlers, not just the RequestMappingHandlerMapping.
public class ExampleFilter extends OncePerRequestFilter {
private DispatcherServlet dispatcherServlet;
#Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
Object handler = getHandlerBean(request);
boolean isHandledController = handler instanceof ExampleController
|| handler instanceof NestedEampleController;
if (!isHandledController) {
filterChain.doFilter(request, response);
return;
}
// ...
}
private Object getHandlerBean(HttpServletRequest request) {
try {
HandlerExecutionChain handlerChain = getHandler(request);
if (handlerChain != null) {
Object handler = handlerChain.getHandler();
if (handler instanceof HandlerMethod) {
return ((HandlerMethod) handler).getBean();
}
}
return null;
} catch (Exception e) {
return null;
}
}
/**
* Duplicates the protected "getHandler" method logic from DispatcherServlet.
*/
private HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
List<HandlerMapping> handlerMappings = dispatcherServlet.getHandlerMappings();
if (handlerMappings != null) {
for (HandlerMapping mapping : handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
#Override
protected void initFilterBean() {
WebApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
dispatcherServlet = appContext.getBean(DispatcherServlet.class);
}
}
I'm not sure if there is a more idiomatic approach, and it definitely feels like it's jumping through some hoops and digging into the Spring internals a bit too much. But it does appear to work, at least on spring-web 5.2.7.RELEASE.

Custom exception handler for status NotFound

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

Implementing the ResponseErrorHandler Interface

I'm trying to override the ResponseErrorHandler interface to be able to return the entire request (status code, body etc.) in case of any response other than 2xx.
I noticed that the Spring (RestTemplate) default returns an exception in case of a response other than 2xx. I do not want to return an exception, I just want to be able to return a:
new ResponseEntity(HttpStatus.STATUS_CODE)
Following some tutorials, I've found the following code:
#Component
public class LoginErrorHandler
implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse httpResponse)
throws IOException {
return (
httpResponse.getStatusCode().series() == CLIENT_ERROR
|| httpResponse.getStatusCode().series() == SERVER_ERROR);
}
#Override
public void handleError(ClientHttpResponse httpResponse)
throws IOException {
if (httpResponse.getStatusCode()
.series() == SERVER_ERROR) {
// handle SERVER_ERROR
} else if (httpResponse.getStatusCode()
.series() == CLIENT_ERROR) {
// handle CLIENT_ERROR
}
}
(Reference)
But I have not understood how I can return a ResponseEntity without changing the method return (which I can not by implementing the method).
Implementation:
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new LoginErrorHandler());
return restTemplate.postForEntity(url, request, String.class);
You can use Spring's ControllerAdvice and ExceptionHandler annotations to handle exceptions through your application. Below code returns 500 http status code if any exception encountered in your request. You can add other Exception classes or your own custom class to handle specific cases and return specific status codes to client.
Edit
Handling each code will not be a good idea. Rather you can wrap them in your custom exception and provide proper message to your client service. Still you can try something like below.
#Component
#ControllerAdvice
public class MyExceptionHandler {
#ExceptionHandler(HttpClientErrorException.BadRequest.class)
#ResponseStatus(code=HttpStatus.BAD_REQUEST, reason="Bad Request", value=HttpStatus.BAD_REQUEST)
public void handleBadRequest(HttpClientErrorException.BadRequest e) {
//handle bad request exception
}
#ExceptionHandler(HttpClientErrorException.NotFound.class)
#ResponseStatus(code=HttpStatus.NOT_FOUND, reason="Not Found", value=HttpStatus.NOT_FOUND)
public void handleNotFound(HttpClientErrorException.NotFound e) {
//handle Not Found
}
#ExceptionHandler(HttpServerErrorException.InternalServerError.class)
#ResponseStatus(code=HttpStatus.INTERNAL_SERVER_ERROR, reason="Internal Server Error", value=HttpStatus.INTERNAL_SERVER_ERROR)
public void handleInternalServerError(HttpServerErrorException.InternalServerError e) {
//handle internal server error
}
//more methods for each code.
}
Then handle the codes from in your rest template as below. Here you won't be able to return body of the response to the client.
#Component
public class LoginErrorHandler
implements ResponseErrorHandler {
#Override
public boolean hasError(ClientHttpResponse httpResponse)
throws IOException {
return (httpResponse.getStatusCode() != HttpStatus.OK);
}
#Override
public void handleError(ClientHttpResponse httpResponse)
throws IOException {
if (httpResponse.getRawStatusCode() >=400 && httpResponse.getRawStatusCode()<500 ) {
throw HttpClientErrorException.create(httpResponse.getStatusCode(), httpResponse.getStatusText(), httpResponse.getHeaders(), null, null);
}else if(httpResponse.getRawStatusCode() >=500){
throw HttpServerErrorException.create(httpResponse.getStatusCode(), httpResponse.getStatusText(), httpResponse.getHeaders(), null, null);
}else {
//throw some other exceptions for other codes and catch them in controller advice.
}
}
}
You can do this:
#Component
#ControllerAdvice
public class LoginErrorHandler{
#ExceptionHandler(HttpClientErrorException.class)
#ResponseBody
public void handleError(HttpClientErrorException e, HttpServletResponse response) throws IOException {
response.sendError(e.getRawStatusCode(), e.getStatusText());
}
}
This you generify all status code that should drop an exception, and it will return in the body.
There are two extensively used and very convenient exception handlers which are provided by Spring framework for centralized exception management in Spring Boot applications.
ResponseEntityExceptionHandler : exception thrown by our endpoint methods(methods annotated with #RequestMapping)
A convenient base class for #ControllerAdvice classes that wish to provide centralized exception handling across all #RequestMapping methods through #ExceptionHandler methods.
ResponseErrorHandler : Spring provides a hook ResponseErrorHandler which can be implemented to handle the exception thrown by external services. To call any external service most likely you will be using a RestTemplate. A RestTemplate can throw three types of exception as listed below :
HttpClientErrorException : For 4xx series status codes or Client errors.
HttpServerErrorException : For 5xx series status codes or server errors
RestClientException : Any other status codes like 3xx series
To simplify things we can handle these exceptions as a catch block separately but it lead to lot of boilerplate code scattered across our service. Strategy interface used by the RestTemplate to determine whether a particular response has an error or not.
There are two steps to using ResponseErrorHandler :
Step 1: Create a custom error handler class by implementing ResponseErrorHandler and implements its methods hasError and handleError
Step 2: We need to inject the ResponseErrorHandler implementation into the RestTemplate instance as follows :
By default in RestTemplate the errorHandler points to DefaultResponseErrorHandler.
Source

How to catch all unhandled exceptions (i.e. without existing #ExceptionHandler) in Spring MVC?

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

Categories

Resources