230

How do I get a Spring 3.0 controller to trigger a 404?

I have a controller with @RequestMapping(value = "/**", method = RequestMethod.GET) and for some URLs accessing the controller, I want the container to come up with a 404.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
NA.
  • 6,451
  • 9
  • 36
  • 36

14 Answers14

347

Since Spring 3.0 you also can throw an Exception declared with @ResponseStatus annotation:

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    ...
}

@Controller
public class SomeController {
    @RequestMapping.....
    public void handleCall() {
        if (isFound()) {
            // whatever
        }
        else {
            throw new ResourceNotFoundException(); 
        }
    }
}
matt b
  • 138,234
  • 66
  • 282
  • 345
axtavt
  • 239,438
  • 41
  • 511
  • 482
  • 2
    Interesting. Can you specify which HttpStatus to use at the throw site (i.e. not have it compiled into the Exception class)? – matt b Jan 14 '10 at 19:46
  • 1
    @mattb: I think the point of `@ResponseStatus` is that you define a whole bunch of strongly-typed, well-named exception classes, each with their own `@ResponseStatus`. That way, you decouple your controller code from the detail of HTTP status codes. – skaffman Jan 14 '10 at 19:50
  • I see. Pretty neat idea. I need to start using the 3.0 annoations and learn all this goodness! – matt b Jan 14 '10 at 19:52
  • 1
    There's a lot of cool stuff in Spring 3 that isn't headline-grabbing... I discovered `@Async` today, and it's bloody nice – skaffman Jan 14 '10 at 21:49
  • I have no clue why there isn't a NonOkStatusException that handles all this; the annotations seem pointlessly verbose. Still, it beats injecting the HttpResponse... – davetron5000 Jan 26 '10 at 19:42
  • 10
    Can this be extended to support returning a body containing more description about the error? – Tom May 27 '11 at 14:23
  • 8
    @Tom: `@ResponseStatus(value = HttpStatus.NOT_FOUND, reason="Your reason")` – Nailgun Nov 21 '12 at 09:13
  • 6
    If you use this ResourceNotFound exception only for flow control, then it is maybe a good idea to override `ResourceNotFound.fillInStackTrace()` with an empty implementation. – Ralph Mar 20 '13 at 16:21
  • 1
    Is there a way to send 404 and also show an custom error page (for page not found). Some one can please give me an example. I am pretty new to Spring – hop Apr 02 '15 at 17:24
  • 1
    From the 4.3 docs: _the Servlet container will typically write an HTML error page therefore making the use of a `reason` unsuitable for REST APIs. For such cases it is preferable to use a `ResponseEntity` as a return type and avoid the use of `ResponseStatus` altogether_ – Andrei Damian-Fekete Jan 24 '18 at 10:55
  • it is bad practice to use exception in "normal" program flow. exceptions should be used in exceptional situations - this is imho a big flaw in spring boot see @ilya serbis answer for better solution – dermoritz Mar 24 '23 at 12:39
130

Starting from Spring 5.0, you don't necessarily need to create additional exceptions:

throw new ResponseStatusException(NOT_FOUND, "Unable to find resource");

Also, you can cover multiple scenarios with one, built-in exception and you have more control.

See more:

Alex R
  • 11,364
  • 15
  • 100
  • 180
Jonatan Ivanov
  • 4,895
  • 2
  • 15
  • 30
37

Rewrite your method signature so that it accepts HttpServletResponse as a parameter, so that you can call setStatus(int) on it.

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-arguments

matt b
  • 138,234
  • 66
  • 282
  • 345
  • 9
    This is the only correct answer if someone is looking for a way to tell the http requestor they made a mistake while not flooding the prod ops team with a bunch of exceptions they can't fix. – Alex R Dec 10 '15 at 02:20
  • 4
    `setStatus(int)` javadoc states as follows: If this method is used to set an error code, then the container's error page mechanism will not be triggered. If there is an error and the caller wishes to invoke an error page defined in the web application, then `sendError` must be used instead. – Philippe Gioseffi Apr 06 '17 at 17:58
  • @AlexR Handled exceptions should not be flooding the ops team. If they are, logging is being done incorrectly. – Raedwald Sep 26 '19 at 10:29
27

Since Spring 3.0.2 you can return ResponseEntity<T> as a result of the controller's method:

@RequestMapping.....
public ResponseEntity<Object> handleCall() {
    if (isFound()) {
        // do what you want
        return new ResponseEntity<>(HttpStatus.OK);
    }
    else {
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
}

(ResponseEntity<T> is a more flexible than @ResponseBody annotation - see another question)

Community
  • 1
  • 1
Ilya Serbis
  • 21,149
  • 6
  • 87
  • 74
  • 2
    flexible of-course but defeats the benefits of declarative programming – rohanagarwal Jun 29 '17 at 07:26
  • 3
    If you are using Sentry or similar in PROD, and don't want to spam it with errors that are no actual errors, this solution is much better compared to the one using exceptions for this non-exceptional situation. – Tobias Hermann Aug 19 '18 at 06:37
  • 1
    Don't forget how to populate the body (with your actual object) . generic "Object" example: Object returnItemBody = new Object(); return ResponseEntity.status(HttpStatus.OK).body(returnItemBody); – granadaCoder May 30 '19 at 19:14
25

I would like to mention that there's exception (not only) for 404 by default provided by Spring. See Spring documentation for details. So if you do not need your own exception you can simply do this:

 @RequestMapping(value = "/**", method = RequestMethod.GET)
 public ModelAndView show() throws NoSuchRequestHandlingMethodException {
    if(something == null)
         throw new NoSuchRequestHandlingMethodException("show", YourClass.class);

    ...

  }
michal.kreuzman
  • 12,170
  • 10
  • 58
  • 70
  • 11
    This looks to be meant for a specific case - when Spring can't find a handler. The case in question is when Spring *can* find a handler, but the user wants to return a 404 for another reason. – Roy Truelove Oct 09 '12 at 19:51
  • 2
    I'm using it when my ulr mapping for handler method is dynamic. When entity doesn't exist based on `@PathVariable` there is no request handling from my point of view. Do you think it's better/cleaner to use your own Exception annotated with `@ResponseStatus(value = HttpStatus.NOT_FOUND) `? – michal.kreuzman Oct 10 '12 at 10:58
  • 1
    In your case it sounds fine, but I don't know that I'd recommend the exceptions found in the link you provided to handle all cases where an exception in necessary - sometimes you should make your own. – Roy Truelove Oct 10 '12 at 13:00
  • Well, Spring provided one exception and one only for 404. They should have named it 404Exception or created one. But as it is now, I think it is ok to throw this whenever you need a 404. – autra Feb 06 '13 at 16:35
  • Well, technically speaking it's okay - you'll send 404 status header. But automatic error message - response content - is "No request handling method with name..." which is probably not something you want to show to user. – Olli Mar 09 '15 at 11:55
17

you can use the @ControllerAdvice to handle your Exceptions , The default behavior the @ControllerAdvice annotated class will assist all known Controllers.

so it will be called when any Controller you have throws 404 error .

like the following :

@ControllerAdvice
class GlobalControllerExceptionHandler {
    @ResponseStatus(HttpStatus.NOT_FOUND)  // 404
    @ExceptionHandler(Exception.class)
    public void handleNoTFound() {
        // Nothing to do
    }
}

and map this 404 response error in your web.xml , like the following :

<error-page>
        <error-code>404</error-code>
        <location>/Error404.html</location>
</error-page>

Hope that Helps .

  • 2
    you have mapped exceptions of type Exception(and subclasses) with a 404 status code. Did you ever think there are internal server errors? How do you plan to handle those in your GlobalControllerExceptionHandler? – rohanagarwal Jun 29 '17 at 07:25
  • This did NOT work for REST controllers, returns an empty response. – rustyx Sep 11 '17 at 18:57
12

While the marked answer is correct there is a way of achieving this without exceptions. The service is returning Optional<T> of the searched object and this is mapped to HttpStatus.OK if found and to 404 if empty.

@Controller
public class SomeController {

    @RequestMapping.....
    public ResponseEntity<Object> handleCall(@PathVariable String param) {
        return  service.find(param)
                .map(result -> new ResponseEntity<>(result, HttpStatus.OK))
                .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }
}

@Service
public class Service{
  
    public Optional<Object> find(String param){
        if(!found()){
            return Optional.empty();
        }
        ...
        return Optional.of(data); 
    }
    
}
catch23
  • 17,519
  • 42
  • 144
  • 217
Liviu Stirb
  • 5,876
  • 3
  • 35
  • 40
  • I like this approach in general, but using Optionals sometimes end up being an anti-pattern. And gets complicated when returning collections. – jfzr Aug 28 '19 at 20:50
10

If your controller method is for something like file handling then ResponseEntity is very handy:

@Controller
public class SomeController {
    @RequestMapping.....
    public ResponseEntity handleCall() {
        if (isFound()) {
            return new ResponseEntity(...);
        }
        else {
            return new ResponseEntity(404);
        }
    }
}
Ralph
  • 118,862
  • 56
  • 287
  • 383
6

I'd recommend throwing HttpClientErrorException, like this

@RequestMapping(value = "/sample/")
public void sample() {
    if (somethingIsWrong()) {
        throw new HttpClientErrorException(HttpStatus.NOT_FOUND);
    }
}

You must remember that this can be done only before anything is written to servlet output stream.

mmatczuk
  • 539
  • 4
  • 9
  • 4
    That exception is thrown by the Spring HTTP client. Spring MVC seems to not recognize that exception. What Spring version are you using? Are you getting a 404 with that exception? – Eduardo May 05 '16 at 21:11
  • 1
    This causes Spring Boot to return: `Whitelabel Error Page \n .... \n There was an unexpected error (type=Internal Server Error, status=500). \n 404 This is your not found error` – slim Mar 09 '17 at 15:31
  • This is an exception for a HTTP client, not for a controller. So using it in the specified context is inappropriate. – Alexey Mar 20 '17 at 13:11
3

This is a bit late, but if you are using Spring Data REST then there is already org.springframework.data.rest.webmvc.ResourceNotFoundException It also uses @ResponseStatus annotation. There is no need to create a custom runtime exception anymore.

pilot
  • 866
  • 11
  • 16
2

Also if you want to return 404 status from your controller all you need is to do this

@RequestMapping(value = "/something", method = RequestMethod.POST)
@ResponseBody
public HttpStatus doSomething(@RequestBody String employeeId) {
    try {
        return HttpStatus.OK;
    } 
    catch (Exception ex) { 
         return HttpStatus.NOT_FOUND;
    }
}

By doing this you will receive a 404 error in case when you want to return a 404 from your controller.

C-Otto
  • 5,615
  • 3
  • 29
  • 62
AbdusSalam
  • 485
  • 5
  • 13
2

Because it's always good to have at least ten ways of doing the same thing:

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Something {
    @RequestMapping("/path")
    public ModelAndView somethingPath() {
        return new ModelAndView("/", HttpStatus.NOT_FOUND);
    }
}
Jason DeMorrow
  • 489
  • 5
  • 9
  • I especially like this answer for two reasons. First it does not use exceptions for control flow. Second it is a perfect fit if you are working with a template engine and want to return a view in success case and NOT_FOUND in a failed case. If you are working on a REST API of course you should look at the answers returning a ResponseEntity. – David Apr 24 '22 at 11:21
0

Configure web.xml with setting

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

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

Create new controller

   /**
     * Error Controller. handles the calls for 404, 500 and 401 HTTP Status codes.
     */
    @Controller
    @RequestMapping(value = ErrorController.ERROR_URL, produces = MediaType.APPLICATION_XHTML_XML_VALUE)
    public class ErrorController {


        /**
         * The constant ERROR_URL.
         */
        public static final String ERROR_URL = "/error";


        /**
         * The constant TILE_ERROR.
         */
        public static final String TILE_ERROR = "error.page";


        /**
         * Page Not Found.
         *
         * @return Home Page
         */
        @RequestMapping(value = "/404", produces = MediaType.APPLICATION_XHTML_XML_VALUE)
        public ModelAndView notFound() {

            ModelAndView model = new ModelAndView(TILE_ERROR);
            model.addObject("message", "The page you requested could not be found. This location may not be current.");

            return model;
        }

        /**
         * Error page.
         *
         * @return the model and view
         */
        @RequestMapping(value = "/500", produces = MediaType.APPLICATION_XHTML_XML_VALUE)
        public ModelAndView errorPage() {
            ModelAndView model = new ModelAndView(TILE_ERROR);
            model.addObject("message", "The page you requested could not be found. This location may not be current, due to the recent site redesign.");

            return model;
        }
}
Atish Narlawar
  • 670
  • 9
  • 15
-1

Simply you can use web.xml to add error code and 404 error page. But make sure 404 error page must not locate under WEB-INF.

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

This is the simplest way to do it but this have some limitation. Suppose if you want to add the same style for this page that you added other pages. In this way you can't to that. You have to use the @ResponseStatus(value = HttpStatus.NOT_FOUND)

Rajith Delantha
  • 713
  • 11
  • 21
  • This the way to do it but consider with it `HttpServletResponse#sendError(HttpServletResponse.SC_NOT_FOUND); return null;` from the controller code. Now from the outside the response looks no different to a normal 404 that did not hit any controller. – Darryl Miles Sep 10 '15 at 08:00
  • 1
    this doesn't trigger a 404, it just handles it if one happens – Alex R Dec 10 '15 at 02:21