404 error redirect in Spring with Java config

As you know, in XML, the way to configure this is:

<error-page>
    <error-code>404</error-code>
    <location>/my-custom-page-not-found.html</location>
</error-page>

But I haven't found a way to do it in Java config. The first way I tried was:

@RequestMapping(value = "/**")
public String Error(){
    return "error";
}

And it appeared to work, but it has conflicts retrieving the resources.

Is there a way to do it?


In Spring Framework, there are number of ways of handing exceptions (and particularly 404 error). Here is a documentation link.

  • First, you can still use error-page tag in web.xml, and customize error page. Here is an example.
  • Second, you can use one @ExceptionHandler for all controllers, like this:

    @ControllerAdvice
    public class ControllerAdvisor {
    
         @ExceptionHandler(NoHandlerFoundException.class)
         public String handle(Exception ex) {
    
            return "404";//this is view name
        }
    }
    

    For this to work, set throwExceptionIfNoHandlerFound property to true for DispatcherServlet in web.xml:

    <init-param>
        <param-name>throwExceptionIfNoHandlerFound</param-name>
        <param-value>true</param-value>
    </init-param>
    

    You can also pass some objects to error view, see javadoc for this.


The most clean solution since spring 4.2 RC3 is using the new createDispatcherServlet hook within the class extending AbstractDispatcherServletInitializer (or indirectly through extending AbstractAnnotationConfigDispatcherServletInitializer) like this:

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    /* ... */

    @Override
    protected DispatcherServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
        final DispatcherServlet dispatcherServlet = super.createDispatcherServlet(servletAppContext);
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
        return dispatcherServlet;
    }
}

Then you can use a global @ControllerAdvice (a class that is annotated with @ControllerAdvice) as described in the reference docs. Within the advice you can handle the NoHandlerFoundException with an @ExceptionHandler as described here.

This could look something like this:

@ControllerAdvice
public class NoHandlerFoundControllerAdvice {

    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<String> handleNoHandlerFoundException(NoHandlerFoundException ex) {
        // prepare responseEntity
        return responseEntity;
    }

}

Use code-based Servlet container initialization as described in the doc and override registerDispatcherServlet method to set throwExceptionIfNoHandlerFound property to true:

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

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

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

    @Override
    protected void registerDispatcherServlet(ServletContext servletContext) {
        String servletName = getServletName();
        Assert.hasLength(servletName, "getServletName() may not return empty or null");

        WebApplicationContext servletAppContext = createServletApplicationContext();
        Assert.notNull(servletAppContext,
            "createServletApplicationContext() did not return an application " +
                    "context for servlet [" + servletName + "]");

        DispatcherServlet dispatcherServlet = new DispatcherServlet(servletAppContext);

        // throw NoHandlerFoundException to Controller
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);

        ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
        Assert.notNull(registration,
            "Failed to register servlet with name '" + servletName + "'." +
                    "Check if there is another servlet registered under the same name.");

        registration.setLoadOnStartup(1);
        registration.addMapping(getServletMappings());
        registration.setAsyncSupported(isAsyncSupported());

        Filter[] filters = getServletFilters();
        if (!ObjectUtils.isEmpty(filters)) {
            for (Filter filter : filters) {
                registerServletFilter(servletContext, filter);
            }
        }

        customizeRegistration(registration);
    }
}    

Then create an exception handler:

@ControllerAdvice
public class ExceptionHandlerController {
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        return "404";// view name for 404 error
    }   
}

Don't forget about using @EnableWebMvc annotation on your Spring configuration file:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages= {"org.project.etc"})
public class WebConfig extends WebMvcConfigurerAdapter {
    ...
}

Simple answer for 100% free xml:

  1. Set properties for DispatcherServlet

    public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[] { RootConfig.class  };
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[] {AppConfig.class  };
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[] { "/" };
        }
    
        @Override
        protected void customizeRegistration(ServletRegistration.Dynamic registration) {
            boolean done = registration.setInitParameter("throwExceptionIfNoHandlerFound", "true"); // -> true
            if(!done) throw new RuntimeException();
        }
    
    }
    
  2. Create @ControllerAdvice:

    @ControllerAdvice
    public class AdviceController {
    
        @ExceptionHandler(NoHandlerFoundException.class)
        public String handle(Exception ex) {
            return "redirect:/404";
        }
    
        @RequestMapping(value = {"/404"}, method = RequestMethod.GET)
        public String NotFoudPage() {
            return "404";
    
        }
    }