34

I'm looking for a clean way to return customized 404 errorpages in Spring 4 when a requested resource was not found. Queries to different domain types should result in different error pages.

Here some code to show my intention (Meter is a domain class):

@RequestMapping(value = "/{number}", method = RequestMethod.GET)
public String getMeterDetails(@PathVariable("number") final Long number, final Model model) {
    final Meter result = meterService.findOne(number);
    if (result == null) {
        // here some code to return an errorpage
    }

    model.addAttribute("meter", result);
    return "meters/details";
}

I imagine several ways for handling the problem. First there would be the possibility to create RuntimeExceptions like

@ResponseStatus(HttpStatus.NOT_FOUND)
public class MeterNotFoundExcption extends RuntimeException { }

and then use an exception handler to render a custom errorpage (maybe containing a link to a list of meters or whatever is appropriate).

But I don't like polluting my application with many small exceptions.

Another possibility would be using HttpServletResponse and set the statuscode manually:

@RequestMapping(value = "/{number}", method = RequestMethod.GET)
public String getMeterDetails(@PathVariable("number") final Long number, final Model model,
final HttpServletResponse response) {
    final Meter meter = meterService.findOne(number);
    if (meter == null) {
        response.setStatus(HttpStatus.NOT_FOUND.value());
        return "meters/notfound";
    }

    model.addAttribute("meter", meter);
    return "meters/details";
}

But with this solution I have to duplicate the first 5 lines for many controller methods (like edit, delete).

Is there an elegant way to prevent duplicating these lines many times?

Christian Rudolph
  • 1,145
  • 1
  • 15
  • 22

8 Answers8

40

The solution is much simpler than thought. One can use one generic ResourceNotFoundException defined as follows:

public class ResourceNotFoundException extends RuntimeException { }

then one can handle errors within every controller with an ExceptionHandler annotation:

class MeterController {
    // ...
    @ExceptionHandler(ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public String handleResourceNotFoundException() {
        return "meters/notfound";
    }

    // ...

    @RequestMapping(value = "/{number}/edit", method = RequestMethod.GET)
    public String viewEdit(@PathVariable("number") final Meter meter,
                           final Model model) {
        if (meter == null) throw new ResourceNotFoundException();

        model.addAttribute("meter", meter);
        return "meters/edit";
    }
}

Every controller can define its own ExceptionHandler for the ResourceNotFoundException.

Christian Rudolph
  • 1,145
  • 1
  • 15
  • 22
  • 10
    Uh, no. This does not really work as intended, because with such exception handling within the controller, the HTTP status of the response when a `meter` is not found will still be `200` instead of `404`. You indeed show a nice page for the user, but the browser will recieve a response indicating that the request has been successfully handled. – Giulio Piancastelli Feb 16 '15 at 14:26
  • 5
    Thanks for your hint. I just rechecked my code and you are right. The `ResponseStatus` annotation should go on the exception handler. I fixed my answer. – Christian Rudolph Feb 16 '15 at 18:06
  • This actually works, in my sample-project! But - how would the method handleResourceNotFoundException get access to the actual ResourceNotFoundException instance object (which, I can give specifics to for disiplaying on the error-page) ? – James Hurst Jan 12 '20 at 19:56
  • 1
    It's really easy to accomplish: just add the Exception to the signature as follows: `handleResourceNotFoundException(ResourceNotFoundException e)` – Christian Rudolph Jan 17 '20 at 18:59
19

modified your web.xml file.Using following code.

<display-name>App Name </display-name>
<error-page>
<error-code>500</error-code>
<location>/error500.jsp</location>
</error-page>

<error-page>
<error-code>404</error-code>
<location>/error404.jsp</location>
</error-page>

Access this by following code.

response.sendError(508802,"Error Message");

Now add this code in web.xml.

<error-page>
<error-code>508802</error-code>
<location>/error500.jsp</location>
</error-page>
Zala Janaksinh
  • 2,929
  • 5
  • 32
  • 58
Youddh
  • 1,511
  • 4
  • 34
  • 51
  • This would be useful, if I wanted to have _one_ customized errorpage for all XyzNotFound cases. But what I want is multiple customized errorpages for multiple different domain classes (one domain class => one errorpage). These might include some hints for the user, where to look further. I clarified my question concerning this. – Christian Rudolph Jan 11 '14 at 12:06
  • With your edited answer I wouldn't be able to return the correct error status code (which is 404) anymore. – Christian Rudolph Jan 11 '14 at 12:37
13

You can map the error codes in web.xml like the following

    <error-page>
        <error-code>400</error-code>
        <location>/400</location>
    </error-page>

    <error-page>
        <error-code>404</error-code>
        <location>/404</location>
    </error-page>

    <error-page>
        <error-code>500</error-code>
        <location>/500</location>
    </error-page>

Now you can create a controller to map the url's that are hit when any of these error is found.

@Controller
public class HTTPErrorHandler{

    String path = "/error";

    @RequestMapping(value="/404")
    public String error404(){
       // DO stuff here 
        return path+"/404";
    }
    }

For full example see my tutorial about this

Nic
  • 6,211
  • 10
  • 46
  • 69
Ekansh Rastogi
  • 2,418
  • 2
  • 14
  • 23
  • This doesn't answer the question. "Queries to different domain types should result in different error pages." – Christian Rudolph Mar 22 '15 at 10:54
  • 1
    @Ekansh thanks for your answer and link looking for this answer from some time +1 – Luffy Sep 17 '15 at 10:06
  • [Don't be a spammer](https://stackoverflow.com/help/promotion), please. I fixed your answer because this seems to be an actual attempt to answer, but in the future, disclose your links on your own. – Nic Oct 29 '18 at 05:00
7

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[] { "/" };
    }
    
    //that's important!!
    @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";
    
    }
    

    }

  3. Create 404.jsp page with any content

That's all.

Alex Reinking
  • 16,724
  • 5
  • 52
  • 86
SerdukovAA
  • 131
  • 2
  • 2
  • As in some of the other answers: This doesn't answer the question. "Queries to different domain types should result in different error pages." - Reading the question's title isn't enough to fully understand the problem... – Christian Rudolph Jan 24 '16 at 20:02
  • 4
    And as of spring 4.2 RC 3 there is a cleaner way to set `throwExceptionIfNoHandlerFound`. Details [here](http://stackoverflow.com/a/31436535/3184859). – Christian Rudolph Jan 24 '16 at 20:05
  • non of other method working. but it's works me. **note:** Must use servlate api 3.10 lower servlet api not work. – Saurav Wahid Feb 06 '17 at 06:36
4

You should follow this article where you can find detailed information about exception handling in Spring MVC projects.

spring-mvc-exception-handling

@ControllerAdvice may help you in this case

Taras
  • 312
  • 3
  • 7
4

We can just add following lines of code into web.xml file and introduce a new jsp file named errorPage.jsp into root directory of the project to get the requirement done.

<error-page>
    <error-code>400</error-code>
    <location>/errorPage.jsp</location>
</error-page>
<error-page>
    <error-code>404</error-code>
    <location>/errorPage.jsp</location>
</error-page>
<error-page>
    <error-code>500</error-code>
    <location>/errorPage.jsp</location>
</error-page>
Manoj Kumar
  • 111
  • 3
1

I also needed to NOT use org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer.

According to org.springframework.web.servlet.DispatcherServlet.setThrowExceptionIfNoHandlerFound(boolean): "Note that if DefaultServletHttpRequestHandler is used, then requests will always be forwarded to the default servlet and a NoHandlerFoundException would never be thrown in that case."

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/DispatcherServlet.html#setThrowExceptionIfNoHandlerFound-boolean-

Before

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.foo.web")
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    configurer.enable();
  }

  // ...
}

After

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.foo.web")
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

  @Override
  public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
  }

  // ...
}
skrueger
  • 146
  • 1
  • 6
  • Actually your override with empty body is equal to default implementation of this method in `WebMvcConfigurerAdapter`, so you can fully remove the overridden method. Link to `throwExceptionIfNoHandlerFound` property is really helpful, thanks. – Yoory N. Jun 06 '18 at 06:12
0

I'm working with a Netbeans project.I added following lines to my web.xml.It only works when I give the path from WEB-INF folder as follows.

    <error-page>
        <error-code>404</error-code>
        <location>/WEB-INF/view/common/errorPage.jsp</location>
    </error-page>
Yasitha Bandara
  • 331
  • 1
  • 4
  • 13