Spring MVC - Mixing web.xml and Spring exception handling

[Updated: Dec 7, 2017, Created: Jan 23, 2017]

Servlet specification allows to map error status codes and exceptions thrown by the web application to the pages or servlets (check out the related tutorial here). The mapping is specified in web.xml. Spring web MVC operates on the servlet specification, so it's still possible to mix the two methods of error handling.

Spring overrides the mapping specified in web.xml. In other words if any exception that is not handled by Spring HandlerExceptionResolver logic then web.xml mapping is used. Let's say if a NullPointerException is mapped to a page in web.xml and at the same time, we have mapped the same exception by using a HandlerExceptionResolver e.g. by using @ExceptionHandler, then Spring handler will be used instead of web.xml's mapping.

In cases when the request doesn't go through the DispatcherServlet and the requested resource doesn't found then 404 status code is returned by the server in response. web.xml allows to map such error codes. Spring is capable to map only application level exceptions, including Spring framework exception as well.


In the following examples, we will see how to mix web.xml and HandlerExceptionResolver mappings and how Spring can override web.xml mapping.



Exception mapping to a JSP page in web.xml

The Controller

@Controller
public class ExampleController {

  @RequestMapping("/test1")
  public String handleRequest1 (Model model) throws Exception {
      model.addAttribute("handler", "handleRequest1");
      throw new RuntimeException("test exception 1");
  }
    .............
}

src/main/webapp/WEB-INF/web.xml

<web-app ...>
 <error-page>
  <exception-type>java.lang.RuntimeException</exception-type>
  <location>/WEB-INF/views/exception-page.jsp</location>
 </error-page>
...
</web-app>

main/webapp/WEB-INF/views/exception-page.jsp

<%@ page language="java"
    contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<html>
<body>
<h3>This is a JSP  exception page</h3>
 <p>Spring Handler method: ${handler}</p>
 <p>
  Status:(javax.servlet.error.status_code)<br/>
        <%=request.getAttribute("javax.servlet.error.status_code") %>
 </p>
  <p>
   Status:(response.getStatus())<br/><%=response.getStatus() %>
  </p>
 <p>
  Reason:<br/><%=request.getAttribute("javax.servlet.error.message") %>
 </p>
 <p>
  Type:<br/><%=request.getAttribute("javax.servlet.error.exception_type") %>
 </p>
</body>
</html>

JavaConfig class

@EnableWebMvc
@ComponentScan("com.logicbig.example")
public class AppConfig {

  @Bean
  public ViewResolver viewResolver () {
      InternalResourceViewResolver viewResolver =
                new InternalResourceViewResolver();
      viewResolver.setPrefix("/WEB-INF/views/");
      viewResolver.setSuffix(".jsp");
      return viewResolver;
  }
    .............
}

Output


We should always test the response header information in an independent client because JSP pages are populated on server side and also JSP pages can populate response header themselves e.g. this can be done after a response information has been printed:

 <%response.setStatus(600);%>

We should check header information in a browser's developer tools or some other http client. Following output is from HTTPie.




Exception mapping to another URI in web.xml

In this example, java.lang.ArithmeticException is mapped to another URI in web.xml, which in turn, is mapped to a controller method in Spring layer:

src/main/webapp/WEB-INF/web.xml

<web-app ...>
 <error-page>
  <exception-type>java.lang.ArithmeticException</exception-type>
  <location>/errorHandler</location>
 </error-page>
.....
 </web-app>

Controller

@Controller
public class ExampleController {
    .............
  @RequestMapping("/test3")
  @ResponseBody
  public String handleRequest3 (Model model) {
      model.addAttribute("handler", "handleRequest3");
      int i = 20 / 0;
      return "testId: " + i;
  }
    .............
  @RequestMapping("/errorHandler")
  public String handleRequest5 (Model model) throws Exception {
      model.addAttribute("handler", "errorHandler");
      return "exception-page";
  }
    .............
}

Output




Mapping status code to a static page in web.xml

<web-app ...>
 ......
 <error-page>
  <error-code>404</error-code>
  <location>/static/custom-not-found-page.html</location>
 </error-page>
.......
</web-app>

src/main/webapp/static/custom-not-found-page.html

<html>
<body>
<p>Oops!! looks like there's nothing available at this location:<br/>
 <b><script>document.write(window.location.href)</script></b>
</p>
</body>
</html>

Registering resource handlers to serve static pages:

@EnableWebMvc
@ComponentScan("com.logicbig.example")
public class AppConfig {
    .............
  @Bean
  public WebMvcConfigurerAdapter webConfigurer () {
      return new WebMvcConfigurerAdapter() {
          @Override
          public void addResourceHandlers (ResourceHandlerRegistry registry) {
              registry.addResourceHandler("/static/**")
                      .addResourceLocations("/static/");
          }
      };
  }
}

Output





@ExceptionHandler mapping within controller

This example demonstrate that Spring default HandlerExceptionResolvers' functionality is not effected by web.xml settings.
We are going to use @ExceptionHandler (related resolver: ExceptionHandlerExceptionResolver)

Controller

@Controller
public class ExampleController {
    .............
  @RequestMapping("/test6")
  public String handleRequest6 (Model model) throws Exception {
      model.addAttribute("handler", "handleRequest6");
      throw new OperationNotSupportedException("test exception 6");
  }
    .............
  @ResponseStatus(HttpStatus.NOT_IMPLEMENTED)
  @ExceptionHandler
  public String handleLocalException (OperationNotSupportedException e,
                                      Model model, HttpServletRequest r) {
      model.addAttribute("handler", "handleLocalException with @ExceptionHandler");
      return "exception-page";
  }
    .............
}

There's no related configuration in JavaConfig class or mapping in web.xml

Output


In above screenshot one thing to notice, 'javax.servlet.error.status_code' is not populated correctly when using @HandlerException (4.3.5.RELEASE). Let's check in HTTPie:




Spring HandlerExceptionResolvers Override the web.xml mapping

This example shows that if same mapping exists in both Spring and web.xml, then Spring's mapping will be used.

web.xml

<web-app ...>
 ....
 <error-page>
  <exception-type>java.lang.IllegalAccessException</exception-type>
  <location>/WEB-INF/views/exception-page.jsp</location>
 </error-page>
 ....
</web-app>

Controller

@Controller
public class ExampleController {
    .............
  @RequestMapping("/test7")
  public String handleRequest7 (Model model) throws Exception {
      model.addAttribute("handler", "handleRequest7");
      throw new IllegalAccessException("test exception 7");
  }
    .............
  @ResponseStatus(HttpStatus.UNAUTHORIZED)
  @ExceptionHandler
  public String handleLocalException2 (IllegalAccessException e,
                                       Model model) {
      model.addAttribute("handler", "handleLocalException2 with @ExceptionHandler");
      return "exception-page";
  }
}

Output

In above screenshot, 'Spring Handler method:' value shows the @ExceptionHandler method is used instead of direct mapping to jsp page in web.xml.




Default mapping to a page in web.xml

<web-app ...>
 ......
 <error-page>
  <location>/WEB-INF/views/default-error-page.jsp</location>
 </error-page>
</web-app>

src/main/webapp/WEB-INF/views/default-error-page.jsp

<%@ page language="java"
    contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>

<html>
<body>
<h3>This is default error page</h3>
 <p>Spring Handler method: ${handler}</p>
 <p>
  Status:<br/><%=request.getAttribute("javax.servlet.error.status_code") %>,
 </p>
 <p>
  Reason:<br/><%=request.getAttribute("javax.servlet.error.message") %>
 </p>
 <p>
  Type:<br/><%=request.getAttribute("javax.servlet.error.exception_type") %>
 </p>
</body>
</html>

Controller

java.lang.Exception is not mapped anywhere.

@Controller
public class ExampleController {
    .............
  @RequestMapping("/test4")
  public String handleRequest4 (Model model) throws Exception {
      model.addAttribute("handler", "handleRequest4");
      throw new Exception("test exception");
  }
    .............
}

output

Overriding default page in Spring

Just like we saw in above example where @ExceptionHandler + IllegalAccessException overrode the web.xml mapping. We can use other HandlerExceptionResolvers to override web.xml's default page

For example following snippet shows SimpleMappingExceptionResolver setting a default page.

  @Bean
  public HandlerExceptionResolver theResolver(){
     SimpleMappingExceptionResolver s = new SimpleMappingExceptionResolver();
     s.setDefaultErrorView("default-error-page2");
     return s;
  }



Spring internal exception handling

Spring internal errors such as MissingPathVariableException are handled by DefaultHandlerExceptionResolver. A mapping like this, in web.xml, will be ignored unless we disable DefaultHandlerExceptionResolver:

<web-app ...>
 ....
 <error-page>
   <exception-type>
             org.springframework.web.bind.MissingPathVariableException
   </exception-type>
   <location>/WEB-INF/views/exception-page.jsp</location>
 </error-page>
 ......
</web-app>

DefaultHandlerExceptionResolver handles the internal exception and sends error information by calling HttpServletResponse.sendError(statusCode). This causes the servlet container (tomcat in our case) to show an error page (not exception page with full stacktrace) along with status code and error message only. Since this exception is treated as handled, the above mapping doesn't apply. We can, however map the status code to a destination like this:

<web-app ...>
....
  <error-page>
   <error-code>500</error-code>
   <location>/WEB-INF/views/exception-page.jsp</location>
  </error-page>
......
</web-app>

Instead of mapping status code to a destination, if there's a default mapping (last section) then that will be mapped.

Let's see an example

web.xml

<web-app ...>
 <error-page>
   <exception-type>
          org.springframework.web.bind.MissingPathVariableException
   </exception-type>
   <location>/WEB-INF/views/exception-page.jsp</location>
 </error-page>
 .....
 <error-page>
  <location>/WEB-INF/views/default-error-page.jsp</location>
 </error-page>
</web-app>

Controller

Following handler will throw MissingPathVariableException because template variable 'id' doesn't match with @PathVariable's value: 'pid'.

@Controller
public class ExampleController {
    .............
  @RequestMapping("/test2/{id}")
  public String handleRequest2 (@PathVariable("pid") String id,
                                Model model) throws Exception {
      model.addAttribute("handler", "handleRequest2");
      return "exception-page";
  }
    .............
}

Output



Note that in all above examples, when exception happens in Spring handler methods, the attributes populated in the Model object are not rendering any value in the error pages. Just like @ExceptionHandler case, where original model attributes are not passed to the exception handler method, a handler method's original model object is not meant to be used in the error page. HandlerExceptionResolver#resolveException method doesn't have original Model information to pass on to the error page.


Example Project

Dependencies and Technologies Used :

  • spring-webmvc 4.3.5.RELEASE: Spring Web MVC.
  • javax.servlet-api 3.0.1 Java Servlet API
  • JDK 1.8
  • Maven 3.3.9

Servlet Spring Mixed Exception Handling Select All Download
  • servlet-spring-mixed-exception-handling
    • src
      • main
        • java
          • com
            • logicbig
              • example
        • webapp
          • WEB-INF
            • views
          • static

See Also