ChatGPT解决这个技术问题 Extra ChatGPT

Trigger 404 in Spring-MVC controller?

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.


m
matt b

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

Interesting. Can you specify which HttpStatus to use at the throw site (i.e. not have it compiled into the Exception class)?
@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.
Can this be extended to support returning a body containing more description about the error?
@Tom: @ResponseStatus(value = HttpStatus.NOT_FOUND, reason="Your reason")
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.
A
Alex R

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


Note that the reason will as of Spring Boot 2.3 not send, due to being an risk to leaking informations.
m
matt b

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


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.
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.
@AlexR Handled exceptions should not be flooding the ops team. If they are, logging is being done incorrectly.
C
Community

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)


flexible of-course but defeats the benefits of declarative programming
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.
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);
m
michal.kreuzman

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);

    ...

  }

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.
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) ?
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.
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.
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.
佚名

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 .


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?
This did NOT work for REST controllers, returns an empty response.
c
catch23

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); 
    }
    
}

I like this approach in general, but using Optionals sometimes end up being an anti-pattern. And gets complicated when returning collections.
R
Ralph

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);
        }
    }
}

m
mmatczuk

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.


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?
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
This is an exception for a HTTP client, not for a controller. So using it in the specified context is inappropriate.
p
pilot

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.


C
C-Otto

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.


J
Jason DeMorrow

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);
    }
}

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.
A
Atish Narlawar

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;
        }
}

R
Rajith Delantha

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)


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.
this doesn't trigger a 404, it just handles it if one happens