Global exception page not selected by Spring MVC - java

Continuing the thread: Global exception page in Apache Tiles and Spring MVC
I have an error page defined in my web.xml:
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/404.jsp</location>
</error-page>
I have noticed one more issue in Spring MVC:
a)
if no #RequestMapping is matched then indeed, my custom error jsp is printed.
b)
if a #RequestMapping is matched, but the method sets an error status eg.
response.setStatus(404);
then Tomcat's (7.0.29) default error page is chosen, not my jsp.
Why? How to make my 404 page be displayed always ?

I think what you're experiencing is caused by the line you mentioned: response.setStatus(404);
This method doesn't trigger the container's error page mechanism, it should be used when there is no error. To trigger the mechanism, you have to use sendError, which is recommended in the official docs.
BTW I've just found out that the behavior differs between Servlet Spec. 2.3 and 2.4
(read here). In 2.3 the two methods are said to do the very same thing, whereas in 2.4 they differ..............

With spring MVC is preferable using build-in exception handler to show error page to the users.
Take a look this tutorial: http://doanduyhai.wordpress.com/2012/05/06/spring-mvc-part-v-exception-handling/

You may want to take a look at ExceptionHandler.
It is really nice and flexible and allows you to implement logic to display different error pages and output different HTTP reponse codes depending on the exception (this is not always a requirement but is nice to know you could do that easily).
I paste here my code as I think it can be useful to solve common issues regarding this topic.
#ExceptionHandler(Exception.class)
public ModelAndView resolveException(Exception ex,
HttpServletRequest request,
HttpServletResponse response) {
// I get an email if something goes wrong so that I can react.
if (enableEmailErrorReporting)
sendExceptionEmail(request.getRequestURL().toString(), ex);
ModelAndView mav = getModelAndView(ex, request);
setStatusCode(ex, response);
return mav;
}
protected ModelAndView getModelAndView(Exception ex,
HttpServletRequest request) {
// Here you can implement custom logic to retrieve the correct
// error page depending on the exception. You should extract
// error page paths as properties or costants.
return new ModelAndView("/WEB-INF/app/error.html");
}
// This is really nice.
// Exceptions can have status codes with the [`ResponseStatus`][2] annotation.
private void setStatusCode(Exception ex, HttpServletResponse response) {
HttpStatus statusCode = HttpStatus.BAD_REQUEST;
ResponseStatus responseStatus =
AnnotationUtils.findAnnotation(ex.getClass(),
ResponseStatus.class);
if (responseStatus != null)
statusCode = responseStatus.value();
response.setStatus(statusCode.value());
}
The logic here is that a controller method throws an uncatched exception. Spring will invoke the method marked with ExceptionHandler (you can have one per controller, per exception, or a global default one, usually I make all my controllers inherit from a BaseController class where I define this method). Passed to the method are the exception itself and any other info you need to choose the right view to display. Even more, you can see if on the exception has been declared a specific HTTP response code (eg, 500 for unchecked exception, 400 for validation errors, etc...) and return that code along with your error page.

Related

ExceptionHandler doesn't catch HandlerInterceptor exception if endpoint path is unknown

I have a component that implements the HandlerInterceptor interface, and implements the preHandle method. In this method I retrieve a parameter from the request, and throw an IllegalArgumentException if that parameter is missing.
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String parameter = request.getHeader("parameter123");
if (StringUtils.isEmpty(parameter)) {
throw new IllegalArgumentException("parameter123 not specified");
}
[...]
return true;
}
In another class annotated with #ControllerAdvice, I have a ExceptionHandler that catches the IllegalArgumentExceptions and turns those into a formatted response with HTTP status code 400.
When this is executed by triggering a valid path of my API, everything works just fine. Problems arise when I try to call an invalid/unexisting path of my API. The HandlerInterceptor is called and the exception is thrown but my ExceptionHandler is not triggered and the result is a basic HTTP status code 500 exception. It seems to both override the basic HTTP status 404 mechanism, while also preventing the triggering of my ExceptionHandlers (even an ExceptionHandler on Exception.class doesn't ever get called).
Any explanations regarding this behaviour are welcome ! Thanks
Although this may be an old question, I want to provide an answer for anyone who may come across it in the future.
When you raise an exception in the preHandle method of a HandlerInterceptor, it may be wrapped in another exception called NestedServletException. This is a specific exception thrown by the Spring framework.
It's worth noting that NestedServletException is a runtime exception that occurs when a servlet or filter throws an exception. It encloses the original exception and provides additional information about the location where the exception occurred.

Redirect inside java interceptor

I would like to create interceptor for error code handling, which will redirect to proper error page in Spring MVC application. The problem is that error code comes from service, which is processed inside the request handler, so I probably need to intercept the service not the request handler.
For example:
#RequestMapping(value = "/products", method = RequestMethod.GET)
public ModelAndView handleProducts(ModelAndView mav) {
//do something
...
//load products
ResponseMo response = productsService.getProducts();
//process the response
mav.addObject("products", response.getData());
...
/do something else
return mav;
}
What I need is to catch the response of getProducts() and if there is error response, I need to redirect... In the project, there are many such handlers and service calls, so I need to solve it on one place. ProductsService is part of the project, same ear.
I was thinking about interceptors, filters, aspects... I can intercept the getProducts() call, but I cannot redirect inside the interceptor, or can I? Or should I use some different approach?
see https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
Create custom exceptions that will fit into your business cases, then handle the exceptions in the controller. Hope it helps.

Spring Boot - Error Controller to handle either JSON or HTML

I have a spring boot application.
I have a custom error controller, that is mapped to using ErrorPage mappings. The mappings are largely based on HTTP Status codes, and normally just render a HTML view appropriately.
For example, my mapping:
#Configuration
class ErrorConfiguration implements EmbeddedServletContainerCustomizer {
#Override public void customize( ConfigurableEmbeddedServletContainer container ) {
container.addErrorPages( new ErrorPage( HttpStatus.NOT_FOUND, "/error/404.html" ) )
}
And my error controller:
#Controller
#RequestMapping
public class ErrorController {
#RequestMapping( value = "/error/404.html" )
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public String pageNotFound( HttpServletRequest request ) {
"errors/404"
}
This works fine - If I just enter a random non-existent URL then it renders the 404 page.
Now, I want a section of my site, lets say /api/.. that is dedicated to my JSON api to serve the errors as JSON, so if I enter a random non-existent URL under /api/.. then it returns 404 JSON response.
Is there any standard/best way to do this? One idea I tried out was to have a #ControllerAdvice that specifically caught a class of custom API exceptions I had defined and returned JSON, and in my standard ErrorController checking the URL and throwing an apprpriate API exception if under that API URL space (but that didn't work, as the ExceptionHandler method could not be invoked because it was a different return type from the original controller method).
Is this something that has been solved?
The problem was my own fault. I was trying to work out why my #ExceptionHandler was not able to catch my exception and return JSON - As I suggested at the end of my question, I thought I was having problems because of conflicting return types - this was incorrect.
The error I was getting trying to have my exception handler return JSON was along the lines of:
"exception": "org.springframework.web.HttpMediaTypeNotAcceptableException",
"message": "Could not find acceptable representation"
I did some more digging/experimenting to try to narrow down the problem (thinking that the issue was because I was in the Spring error handling flow and in an ErrorController that was causing the problem), however the problem was just because of the content negotiation stuff Spring does.
Because my errorPage mapping in the web.xml was mapping to /error/404.html, Spring was using the suffix to resolve the appropriate view - so it then failed when I tried to return json.
I have been able to resolve the issue by changing my web.xml to /error/404 or by turning off the content negotiation suffix option.
Now, I want a section of my site, lets say /api/.. that is dedicated
to my JSON api to serve the errors as JSON, so if I enter a random
non-existent URL under /api/.. then it returns 404 JSON response.
Is there any standard/best way to do this? One idea I tried out was to
have a #ControllerAdvice that specifically caught a class of custom
API exceptions I had defined and returned JSON, and in my standard
ErrorController checking the URL and throwing an apprpriate API
exception if under that API URL space (but that didn't work, as the
ExceptionHandler method could not be invoked because it was a
different return type from the original controller method).
I think you need to rethink what you are trying to do here. According to HTTP response codes here
The 404 or Not Found error message is an HTTP standard response code
indicating that the client was able to communicate with a given
server, but the server could not find what was requested.
So when typing a random URL you may not want to throw 404 all the time. If you are trying to handle a bad request you can do something like this
#ControllerAdvice
public class GlobalExceptionHandlerController {
#ExceptionHandler(NoHandlerFoundException.class)
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public ResponseEntity<ErrorResponse> noRequestHandlerFoundExceptionHandler(NoHandlerFoundException e) {
log.debug("noRequestHandlerFound: stacktrace={}", ExceptionUtils.getStackTrace(e));
String errorCode = "400 - Bad Request";
String errorMsg = "Requested URL doesn't exist";
return new ResponseEntity<>(new ErrorResponse(errorCode, errorMsg), HttpStatus.BAD_REQUEST);
}
}
Construct ResponseEntity that suites your need.

Custom handling for 405 error with Spring Web MVC

In my application, I have a few RequestMappings that only allow POST. If someone happens to fire a GET request at that particular path, they get a 405 error page fed by the container (Tomcat). I can create and define a 405 error page in web.xml for customization.
What I want: any request that would result in a 405 error should be handled in a specific controller method.
What I've tried:
a method with "method = GET" as a counterpart for each of the mappings mentioned. This works fine, but requires me to create an actual requestmapping and method for every path that only allows POST. I find this unnecessary duplication and clutter.
a global 'catch' method (requestmapping /*): this does not work, as Spring takes the GET method to be a wrong call to the path specified with POST only
an ExceptionHandler-annotated method to handle exceptions of class HttpRequestMethodNotSupportedException: this does not work. It seems that Spring throws and catches this exception entirely in its framework code.
specify my own 405 in web.xml. This is not perfect, as I want to have customized handling rather than a static error page.
I would suggest using a Handler Exception Resolver. You can use spring's DefaultHandlerExceptionResolver. Override handleHttpRequestMethodNotSupported() method and return your customized view. This will work across all of your application.
The effect is close to what you were expecting in your option 3. The reason your #ExceptionHandler annotated method never catches your exception is because these ExceptionHandler annotated methods are invoked after a successful Spring controller handler mapping is found. However, your exception is raised before that.
Working Code:
#ControllerAdvice
public class GlobalExceptionController {
#ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ModelAndView handleError405(HttpServletRequest request, Exception e) {
ModelAndView mav = new ModelAndView("/405");
mav.addObject("exception", e);
//mav.addObject("errorcode", "405");
return mav;
}
}
In Jsp page (405.jsp):
<div class="http-error-container">
<h1>HTTP Status 405 - Request Method not Support</h1>
<p class="message-text">The request method does not support. home page.</p>
</div>
You can use spring DefaultHandlerExceptionResolver.
Override handleHttpRequestMethodNotSupported() method and return your customized.
Status Code: 405
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ApiError apiError = ApiError.builder()
.status(HttpStatus.METHOD_NOT_ALLOWED)
.message(ex.getMessage())
.build();
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
}
Response Result:
{
"status": "METHOD_NOT_ALLOWED",
"message": "Request method 'POST' not supported",
"errors": null
}

handleNoSuchRequestHandlingMethod override not working

#Controller
public class CentralizedExceptionController extends DefaultHandlerExceptionResolver {
#Override
protected ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("working?!");
return new ModelAndView();
}
I have this in my code, but in case of a 404 its never called.
(I dont have an error-page defined in my web.xml, and i dont want to)
Take a look at this jira issue: https://jira.springsource.org/browse/SPR-8837?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=72648#comment-72648
If your Spring dispatcher servlet is configured to process all/most URLs, then you are probably getting the 404 error along with this DispatcherServlet log message from console:
No mapping found for HTTP request with URI [xxx]
This indicates that Spring's DispatcherServlet is processing the request but do not have an appropriate #RequestMapping to dispatch to.
A simple solution would be to limit requests processed by dispatcher servlet by reconfiguring web.xml's servlet-mapping > url-pattern to only URLs specified by your application's #RequestMappings. However, this is NOT very practical (so don't do this).
One way to overcome this would be to create a #RequestMapping that handles all "unhandled" request mappings - some kind of fallback request mapping.
#RequestMapping("**")
#ResponseBody
public String fallbackRequestMapping() {
return "do something useful...";
}
Note that this answer is similar in approach to Dani's answer but written with annotation based development in mind. Therefore, it is useful to understand the associated Spring issue.
plz check. Your controller class name should not be Controller.java.
You need to use #ExceptionHandler annotation to your method:
#ExceptionHandler(NoSuchRequestHandlingMethodException.class)
public ModelAndView handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex, HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
}

Categories

Resources