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:
-
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(); } }
-
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"; } }