We have seen the basic concept and a simple usage of the low-level HandlerExceptionResolver interface.
In this tutorial, we are going to demonstrate how to create a custom HandlerExceptionResolver to introduce a new way to handle exceptions in Spring MVC.
Example
Steps:
- Create a new annotation,
ErrorView, which specifies a view name and a status code.
- Create a custom HandlerExceptionResolver implementation, HandlerExceptionToViewResolver, whose resolveException() method checks whether the handler method where the exception occurred has the ErrorView annotation. If it does, the resolver reads the view name and status code from the annotation, sets that status code on the response, and returns the corresponding error view. If the handler method does not have the ErrorView annotation, default error processing continues.
- Create a controller and a JSP page for testing.
Creating the custom ErrorView annotation
package com.logicbig.example;
import org.springframework.http.HttpStatus;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ErrorView {
String value ();
HttpStatus status () default HttpStatus.INTERNAL_SERVER_ERROR;
}
Creating the custom HandlerExceptionResolver
package com.logicbig.example;
import org.springframework.http.HttpStatus;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
public class HandlerExceptionToViewResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException (HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
ErrorView errorView = hm.getMethodAnnotation(ErrorView.class);
if (errorView != null) {
//preparing ModelAndView
String viewName = errorView.value();
ModelAndView model = new ModelAndView(viewName);
model.addObject("requestUri", request.getRequestURI());
model.addObject("exception", ex);
HttpStatus status = errorView.status();
model.addObject("statusValue", status.value());
model.addObject("statusStr", status.getReasonPhrase());
//setting status code
response.setStatus(status.value());
return model;
}
}
//returning null for default processing
return null;
}
}
Note: as of Spring Framework 6 (and continuing in Spring Framework 7), HttpServletRequest and HttpServletResponse come from the jakarta.servlet.http package. The javax.servlet package used in older Spring 4/5 projects is no longer applicable.
Registering the custom HandlerExceptionResolver
package com.logicbig.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import java.util.List;
@EnableWebMvc
@ComponentScan
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void extendHandlerExceptionResolvers(
List<HandlerExceptionResolver> resolvers) {
resolvers.add(new HandlerExceptionToViewResolver());
}
@Bean
public ViewResolver viewResolver () {
InternalResourceViewResolver viewResolver =
new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
Creating the Controller
In the following controller, we create two handler methods: the first is annotated with @ErrorView, and the second is not. The second method throws an exception that is meant to be handled by the @ExceptionHandler method (the third method); this confirms that the default exception-handling processing still works as expected.
package com.logicbig.example;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.naming.OperationNotSupportedException;
@Controller
public class ExampleController {
@ErrorView(value = "test-error-view", status = HttpStatus.GONE)
@RequestMapping("/test")
public String handleRequest () throws Exception {
throw new Exception("test exception");
}
@RequestMapping("/test2")
public String handleRequest2 () throws Exception {
throw new OperationNotSupportedException("exception thrown in test2");
}
@ExceptionHandler
@ResponseBody
public String handleException (OperationNotSupportedException e) {
return "exception :" + e.toString();
}
}
JSP error page
src/main/webapp/WEB-INF/views/test-error-view.jsp<%@ page language="java"
contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<html>
<body>
<h3>Test Error View</h3>
<p>Request Uri: <b>${requestUri}</b></p>
<p>Exception: <b>${exception['class'].name}</b></p>
<p>Message: <b>${exception.message}</b></p>
<p>Response status: <b>${statusValue} (${statusStr})</b></p>
</body>
</html>
Note: JSP is still supported under the current Jakarta EE baseline, but it is no longer the default or most commonly recommended view technology in new Spring MVC projects; Thymeleaf is generally favored instead.
Running the example
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
Output
/test
$ curl -si "http://localhost:8080/test" | findstr /V /R "^Date: ^Content- ^Server: ^Set-Cookie: ^Expires:" HTTP/1.1 410 Gone
<html> <body> <h3>Test Error View</h3> <p>Request Uri: <b>/test</b></p> <p>Exception: <b>java.lang.Exception</b></p> <p>Message: <b>test exception</b></p> <p>Response status: <b>410 (Gone)</b></p> </body> </html>
/test2
$ curl -s "http://localhost:8080/test2" exception :javax.naming.OperationNotSupportedException: exception thrown in test2
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 3.2.9.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: Versions in green have been tested.
- jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
- JDK 25
- Maven 3.9.11
|