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();
}
}
}
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:
ResponseStatusException (javadoc)
https://www.baeldung.com/spring-response-status-exception
Rewrite your method signature so that it accepts HttpServletResponse
as a parameter, so that you can call setStatus(int)
on it.
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.
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)
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);
...
}
@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)
?
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 .
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);
}
}
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);
}
}
}
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.
Whitelabel Error Page \n .... \n There was an unexpected error (type=Internal Server Error, status=500). \n 404 This is your not found error
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.
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.
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);
}
}
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;
}
}
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)
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.
Success story sharing
@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.@ResponseStatus(value = HttpStatus.NOT_FOUND, reason="Your reason")
ResourceNotFound.fillInStackTrace()
with an empty implementation.