Close

Spring MVC - Customizing Spring default HandlerExceptionResolvers functionality

[Last Updated: Sep 15, 2026]

This example demonstrates how to modify the default HandlerExceptionResolver functionality in Spring MVC.

The use case we are going to use is: we will map a Spring internal exception to a custom error page. By default, Spring internal exceptions are processed by DefaultHandlerExceptionResolver. This example demonstrates how to selectively modify that default behavior. We will use SimpleMappingExceptionResolver to achieve the desired result.

Prerequisite

Ordering and customization of default HandlerExceptionResolvers.`


Example

Creating SimpleMappingExceptionResolver and registering it as a bean

package com.logicbig.example;

import org.springframework.context.annotation.*;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.*;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

import java.util.Properties;

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

    @Bean
    HandlerExceptionResolver customExceptionResolver () {
        SimpleMappingExceptionResolver s = new SimpleMappingExceptionResolver();
        Properties p = new Properties();
        //mapping spring internal error NoHandlerFoundException to a view name.
        p.setProperty(NoHandlerFoundException.class.getName(), "error-page");
        s.setExceptionMappings(p);
        //uncomment following line if we want to send code other than default 200
        //s.addStatusCode("error-page", HttpStatus.NOT_FOUND.value());

        //This resolver will be processed before default ones
        s.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return s;
    }

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

The above SimpleMappingExceptionResolver maps Spring's internal NoHandlerFoundException to a custom page; the rest of the default functionality remains unchanged.


WebApplicationInitializer implementation

package com.logicbig.example;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FrameworkServlet;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class AppInitializer extends
        AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{AppConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected DispatcherServlet createDispatcherServlet(WebApplicationContext wac) {
        DispatcherServlet ds = new DispatcherServlet(wac);
        //setting this flag to true will throw NoHandlerFoundException instead of 404 page
       // removed in spring 7 ds.setThrowExceptionIfNoHandlerFound(true);
        return ds;
    }
}

What is throwExceptionIfNoHandlerFound?

NoHandlerFoundException is only thrown if the throwExceptionIfNoHandlerFound property of DispatcherServlet is set to true. In the example below, this is done via DispatcherServlet#setThrowExceptionIfNoHandlerFound(true). In the Spring version this example is pinned to (4.3.5.RELEASE), that property defaults to false.

Note: starting Spring Framework 6.1, throwExceptionIfNoHandlerFound defaults to true, so DispatcherServlet always raises NoHandlerFoundException when no handler is found; the property no longer needs to be set explicitly. As of Spring Framework 7.0, the setThrowExceptionIfNoHandlerFound(boolean)/getThrowExceptionIfNoHandlerFound() methods have been removed from DispatcherServlet entirely.


JSP page

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

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

<html>
<body>
<h3>This is custom Spring exception page</h3>
 <p>Exception: <b>${exception.message}</b></p>

</body>
</html>

The Controller

package com.logicbig.example;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class ExampleController {

    /**
     * This will throw MissingPathVariableException with response code 500
     */
    @RequestMapping("/test/{id}")
    @ResponseBody
    public String handleRequest (@PathVariable("testId") String id) {
       return "id: "+id;
    }

    /**
     * The exception should be processed by @ExceptionHandler method
     */
    @RequestMapping("/test")
    public String handleRequest2 () throws Exception {
        throw new IllegalStateException();
    }

    @ExceptionHandler
    @ResponseBody
    public String handleException (IllegalStateException b) {
        return "from @ExceptionHandler method: " + b ;
    }
}

Running The Example

To try examples, run embedded Jetty (configured in pom.xml of example project below):

mvn jetty:run

output

URI: /test/{id}

This should throw MissingPathVariableException, which is not mapped by our SimpleMappingExceptionResolver.

The above exception is handled by DefaultHandlerExceptionResolver.


URI: /test

This should invoke the handleException(..) method of our controller. This method is annotated with @ExceptionHandler and has a parameter of the matching exception type.

This shows that ExceptionHandlerExceptionResolver is processed as expected.


Other URIs

Other URIs would normally return the container's default 404 error page. In our case, we configured DispatcherServlet to throw NoHandlerFoundException, which is mapped to "error-page" by our SimpleMappingExceptionResolver.

Let's check the response status code using Chrome's DevTools (accessed via F12).

If SimpleMappingExceptionResolver doesn't set any status code explicitly, the code 200 is returned. If we want to return 404 instead, we can use
SimpleMappingExceptionResolver#addStatusCode("error-page", 404);

Integration Test

package com.logicbig.example;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.NoHandlerFoundException;
import static org.hamcrest.Matchers.instanceOf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextConfiguration(classes = AppConfig.class)
public class ExampleControllerTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @BeforeEach
    public void setup() {
        mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
    }

    @Test
    public void testMissingPathVariableMessage() throws Exception {
        mockMvc.perform(get("/test/4"))
               .andExpect(status().isInternalServerError())
               .andExpect(status().reason(
                       "Required path variable 'testId' is not present."));
    }

    @Test
    public void testExceptionHandlerMethod() throws Exception {
        mockMvc.perform(get("/test"))
               .andExpect(status().isOk())
               .andExpect(content().string(
                       "from @ExceptionHandler method: "
                               + "java.lang.IllegalStateException"));
    }

    @Test
    public void testNoHandlerFound() throws Exception {
        mockMvc.perform(get("/otherPage"))
               .andExpect(status().isOk())
               .andExpect(model().attribute("exception",
                                            instanceOf(NoHandlerFoundException.class)));
    }
}

Output

$ mvn clean test -Dtest="ExampleControllerTest"
[INFO] Scanning for projects...
[INFO]
[INFO] ---< com.logicbig.example:default-exception-resolver-customization >----
[INFO] Building default-exception-resolver-customization 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ default-exception-resolver-customization ---
[INFO] Deleting D:\example-projects\spring-mvc\default-exception-resolver-customization\target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ default-exception-resolver-customization ---
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\default-exception-resolver-customization\src\main\resources
[INFO]
[INFO] --- compiler:3.16.0:compile (default-compile) @ default-exception-resolver-customization ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 3 source files with javac [debug target 25] to target\classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ default-exception-resolver-customization ---
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\default-exception-resolver-customization\src\test\resources
[INFO]
[INFO] --- compiler:3.16.0:testCompile (default-testCompile) @ default-exception-resolver-customization ---
[INFO] Recompiling the module because of changed dependency.
[INFO] Compiling 1 source file with javac [debug target 25] to target\test-classes
[INFO]
[INFO] --- surefire:3.2.5:test (default-test) @ default-exception-resolver-customization ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.ExampleControllerTest
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.040 s -- in com.logicbig.example.ExampleControllerTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.541 s
[INFO] Finished at: 2026-09-16T02:37:03-05:00
[INFO] ------------------------------------------------------------------------
INFO: Completed initialization in 2 ms
INFO: Completed initialization in 2 ms
INFO: Completed initialization in 1 ms
WARNING: No mapping for GET /otherPage

Example Project

Dependencies and Technologies Used:

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

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
    • 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.

  • spring-test 7.0.6 (Spring TestContext Framework)
  • jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
  • junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
  • hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
  • JDK 25
  • Maven 3.9.11

Spring MVC - Customizing default HandlerExceptionResolvers Select All Download
  • default-exception-resolver-customization
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • AppConfig.java
          • webapp
            • WEB-INF
              • views
        • test
          • java
            • com
              • logicbig
                • example

    See Also

    Join