Close

Spring MVC - Using multiple HandlerExceptionResolvers together

[Last Updated: Sep 18, 2026]

We can use multiple HandlerExceptionResolver instances at once by combining them with HandlerExceptionResolverComposite.

java.lang.ObjectObjectorg.springframework.web.servlet.handler.HandlerExceptionResolverCompositeHandlerExceptionResolverCompositeorg.springframework.web.servlet.HandlerExceptionResolverHandlerExceptionResolverorg.springframework.core.OrderedOrderedLogicBig

The following method of HandlerExceptionResolverComposite can be used to specify multiple HandlerExceptionResolvers:

public void setExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers)

In HandlerExceptionResolverComposite#resolveException(...), the provided list of resolvers are iterated. The first one to return a non-null ModelAndView wins; otherwise null is returned.

Note that if any of the provided HandlerExceptionResolver instances implements the Ordered interface, the value returned by Ordered#getOrder() is not used to sort the resolvers — they are iterated in the list's insertion order instead. So what is Ordered#getOrder() actually used for? It is used by DispatcherServlet to order the default and application-registered exception resolvers before they are assembled into the composite (we'll look at this with an example in the next tutorial).

In the following example, we'll configure two HandlerExceptionResolvers with HandlerExceptionResolverComposite: one is the custom resolver from our last tutorial example, and the other is SimpleMappingExceptionResolver.


Example

Registering HandlerExceptionResolvers in JavaConfig class

package com.logicbig.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
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.handler.HandlerExceptionResolverComposite;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

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

    @Override
    public void extendHandlerExceptionResolvers(
            List<HandlerExceptionResolver> resolvers) {
        HandlerExceptionToViewResolver r = new HandlerExceptionToViewResolver();

        SimpleMappingExceptionResolver s = new SimpleMappingExceptionResolver();
        s.setDefaultErrorView("default-error-page");
        Properties p = new Properties();
        p.setProperty(IllegalAccessException.class.getName(),
                      "simple-test-error-view");
        s.setExceptionMappings(p);

        HandlerExceptionResolverComposite c = new HandlerExceptionResolverComposite();
        c.setExceptionResolvers(Arrays.asList(r, s));
        resolvers.add(c);
    }

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

The Controller

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 {

    //should be handled by our custom resolver because of @ErrorView
    @ErrorView(value = "test-error-view", status = HttpStatus.GONE)
    @RequestMapping("/test1")
    public String handleRequest () throws Exception {
        throw new Exception("test exception 1");
    }

    //should be handled by handleException() because of @ExceptionHandler.
    //Default resolvers are processed before application registered ones
    @RequestMapping("/test2")
    public String handleRequest2 () throws Exception {
        throw new OperationNotSupportedException("test exception 2");
    }

    //should be handled by SimpleMappingExceptionResolver's default view
    @RequestMapping("/test3")
    public String handleRequest3 () throws Exception {
        throw new Exception("test exception 3");
    }

    //should be handled by SimpleMappingExceptionResolver
    //  IllegalAccessException is mapped to view 'test-error-view'
    @RequestMapping("/test4")
    public String handleRequest4 () throws Exception {
        throw new IllegalAccessException("test exception 4");
    }

    //should be handled by our custom resolver even though IllegalAccessException
    //is mapped in SimpleMappingExceptionResolver. Reason: in iteration order
    // our custom resolver is first to process
    @ErrorView(value = "test-error-view", status = HttpStatus.FORBIDDEN)
    @RequestMapping("/test5")
    public String handleRequest5 () throws Exception {
        throw new IllegalAccessException("test exception 5");
    }

    @ExceptionHandler
    @ResponseBody
    public String handleException (OperationNotSupportedException e) {
        return "exception :" + e.toString();
    }
}

JSP pages

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>

src/main/webapp/WEB-INF/views/simple-test-error-view.jsp

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

<html>
<body>
<h3>Simple Test Error View</h3>
 <p>Exception: <b>${exception['class'].name}</b></p>
 <p>Message: <b>${exception.message}</b></p>
</body>
</html>

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 the default exception page</h3>
 <p>Exception: <b>${exception['class'].name}</b></p>
 <p>Message: <b>${exception.message}</b></p>
</body>
</html>

Running the example

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

mvn jetty:run

output

/test1


/test2


/test3


/test4


/test5


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 static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

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

    @Autowired
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

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

    @Test
    public void test1() throws Exception {
        mockMvc.perform(get("/test1"))
               .andExpect(status().isGone())
               .andExpect(view().name("test-error-view"));
    }

    @Test
    public void test2() throws Exception {
        mockMvc.perform(get("/test2"))
               .andExpect(status().isOk())
               .andExpect(content().string(containsString("test exception 2")));
    }

    @Test
    public void test3() throws Exception {
        mockMvc.perform(get("/test3"))
               .andExpect(view().name("default-error-page"));
    }

    @Test
    public void test4() throws Exception {
        mockMvc.perform(get("/test4"))
               .andExpect(view().name("simple-test-error-view"));
    }

    @Test
    public void test5() throws Exception {
        mockMvc.perform(get("/test5"))
               .andExpect(status().isForbidden())
               .andExpect(view().name("test-error-view"));
    }
}
mvn clean test -Dtest="ExampleControllerTest"

Output

$ mvn clean test -Dtest="ExampleControllerTest"
[INFO] Scanning for projects...
[INFO]
[INFO] -----< com.logicbig.example:handler-exception-resolver-composite >------
[INFO] Building handler-exception-resolver-composite 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ handler-exception-resolver-composite ---
[INFO] Deleting D:\example-projects\spring-mvc\handler-exception-resolver-composite\target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ handler-exception-resolver-composite ---
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\handler-exception-resolver-composite\src\main\resources
[INFO]
[INFO] --- compiler:3.3:compile (default-compile) @ handler-exception-resolver-composite ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 5 source files to D:\example-projects\spring-mvc\handler-exception-resolver-composite\target\classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ handler-exception-resolver-composite ---
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\handler-exception-resolver-composite\src\test\resources
[INFO]
[INFO] --- compiler:3.3:testCompile (default-testCompile) @ handler-exception-resolver-composite ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to D:\example-projects\spring-mvc\handler-exception-resolver-composite\target\test-classes
[INFO]
[INFO] --- surefire:3.2.5:test (default-test) @ handler-exception-resolver-composite ---
[INFO] Using auto detected provider org.apache.maven.surefire.junit4.JUnit4Provider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.ExampleControllerTest
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.514 s -- in com.logicbig.example.ExampleControllerTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.620 s
[INFO] Finished at: 2026-09-14T21:41:40-05:00
[INFO] ------------------------------------------------------------------------

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.

  • 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 - HandlerExceptionResolverComposite Example Select All Download
  • handler-exception-resolver-composite
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • AppConfig.java
          • webapp
            • WEB-INF
              • views
        • test
          • java
            • com
              • logicbig
                • example

    See Also

    Join