Close

Spring MVC - Creating a Custom HandlerExceptionResolver

[Last Updated: Sep 14, 2026]

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:

  1. Create a new annotation, ErrorView, which specifies a view name and a status code.
  2. 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.
  3. 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 Project

Dependencies and Technologies Used:

  • spring-webmvc 7.0.6 (Spring Web MVC)
     Version Compatibility: 3.2.9.RELEASE - 7.0.6Version List
    ×

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
    • 3.2.9.RELEASE
    • 3.2.10.RELEASE
    • 3.2.11.RELEASE
    • 3.2.12.RELEASE
    • 3.2.13.RELEASE
    • 3.2.14.RELEASE
    • 3.2.15.RELEASE
    • 3.2.16.RELEASE
    • 3.2.17.RELEASE
    • 3.2.18.RELEASE
    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • javax.servlet-api:4.x
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • 5.3.32
    • 5.3.33
    • 5.3.34
    • 5.3.35
    • 5.3.36
    • 5.3.37
    • 5.3.38
    • 5.3.39
    • javax.* -> jakarta.*
      jakarta.servlet-api:6.x
      Java 17 min
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.0.16
    • 6.0.17
    • 6.0.18
    • 6.0.19
    • 6.0.20
    • 6.0.21
    • 6.0.22
    • 6.0.23
    • 6.1.0
    • 6.1.1
    • 6.1.2
    • 6.1.3
    • 6.1.4
    • 6.1.5
    • 6.1.6
    • 6.1.7
    • 6.1.8
    • 6.1.9
    • 6.1.10
    • 6.1.11
    • 6.1.12
    • 6.1.13
    • 6.1.14
    • 6.1.15
    • 6.1.16
    • 6.1.17
    • 6.1.18
    • 6.1.19
    • 6.1.20
    • 6.1.21
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 6.2.17
    • 6.2.18
    • 6.2.19
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4
    • 7.0.5
    • 7.0.6

    Versions in green have been tested.

  • jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
  • JDK 25
  • Maven 3.9.11

Spring MVC - Custom HandlerExceptionResolver Select All Download
  • custom-handler-exception-resolver
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • HandlerExceptionToViewResolver.java
          • webapp
            • WEB-INF
              • views

    See Also

    Join