javaspringspring-mvc

Spring MVC: How to return custom 404 errorpages?


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?


Solution

  • 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.