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.
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.
http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-arguments
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);
...
}
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.
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)
Related
I am writing a Spring Boot application. I have written a simple controller that gets invoked whenever the endpoint is hit, but it still returns status 404 and not the specified return value.
HelloController
#Controller
public class MessageRequestController {
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
public String hello() {
System.out.println("Hit me!");
return "Hello, you!";
}
}
Now whenever I call localhost:8080/hello, I see the console log "Hit me!", but "Hello, you!" is never returned. Postman outputs:
{
"timestamp": 1516953147719,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/hello"
}
Application.java
#SpringBootApplication
#ComponentScan({"com.sergialmar.wschat"}) // this is the root package of everything
#EntityScan("com.sergialmar.wschat")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Change your method return a ResponseEntity<T>
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
public ResponseEntity<String> hello() {
System.out.println("Hit me!");
return new ResponseEntity<String>("Hello, you!", HttpStatus.OK);
}
or change the controller to RestController
#RestController
public class MessageRequestController {...}
CURL
ubuntu:~$ curl -X GET localhost:8080/hello
Hello, you!
Short version:
Annotate your endpoint method with ResponseBody to bind the return value to the response body.
#Controller
public class MessageRequestController {
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
#ResponseBody
public String hello() {
System.out.println("Hit me!");
return "Hello, you!";
}
}
You can instead annotate your class with RestController instead of Controller to apply ResponseBody to each method of the class.
#RestController
public class MessageRequestController {
#RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "application/json")
public String hello() {
System.out.println("Hit me!");
return "Hello, you!";
}
}
With #Controller, you use the default model-view from Spring Web MVC, and you're actually telling spring to render the view called Hello, you!.tml from your resources directory (src/main/resources/templates for a Spring Boot project, if I remember correctly).
You can read this article for more information about the Spring MVC REST Workflow.
Once you're more familiar with those concepts, you can even further customize your endpoint method using ResponseEntity.
As you see the "hit me", there's no mapping issue, but in your #RequestMapping annotation you specifie a produce type to "application/json" and you return a simple poor String not formatted and without any header('Content-Type: application/json').
Add the header and format the outpout.
When everything seems ok but receive 404, check this answer:
As you know:
In the Spring MVC you can return view as a String or ModelAndView object.
IMPORTANT NOTE:
In both cases you have to pay attention to relative/absolute path:
If you declare / in the beginning of the view name, you are using absolute path.
Namely it does not matter class level #RequestMapping and directly introduce itself as the final view name.
If you do not declare / in the beginning of the view name, you are using relative path (relative to the class path) and therefore it appends to the class level #RequestMapping to construct final view name.
So, you have to consider the above notes when use the Spring MVC.
Example:
1. create two HTML file test1.html and test2.html in the static folder of spring (boot) structure:
Please note that the class level #RequestMapping behaves as a folder path in the case of relative path.
--- resources
--- static
--- classLevelPath //behaves as a folder when we use relative path scenario in view names
--- test2.html //this will be used for relative path [case (2)]
--- test1.html //this will be used for absolute path [case (1)]
create a controller class like as the below. This example shows different cases with return String and ModelAndView in both relative and absolute path.
#Controller
#RequestMapping("/classLevelPath")
public class TestController {
//case(1)
#RequestMapping("/methodLevelAbsolutePath1")
public String absolutePath1(Model model){
//model.addAttribute();
//...
return "/test1.html";
}
//case(1)
#RequestMapping("/methodLevelAbsolutePath2")
public ModelAndView absolutePath2(Model model){
ModelAndView modelAndView = new ModelAndView("/test1.html");
//modelAndView.addObject()
//....
return modelAndView;
}
//case(2)
#RequestMapping("/methodLevelRelativePath1")
public String relativePath1(Model model){
//model.addAttribute();
//...
return "test2.html";
}
//case(2)
#RequestMapping("/methodLevelRelativePath2")
public ModelAndView relativePath2(Model model){
ModelAndView modelAndView = new ModelAndView("test2.html");
//modelAndView.addObject()
//....
return modelAndView;
}
}
Note:
You can specify the suffix of your view files by a ViewResolver (for example InternalResourceViewResolver or spring.mvc.view.suffix=.html in the appliction.properties file of Spring Boot and do not declare .html suffix in the above code.
Best Regard
I have these 2 resources
#Path("/orders")
public class OrderResource {
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response getOrder(#PathParam("id") String orderid)
throws JSONException {
Order order = db.getOrder(orderid);
return Response.status(Status.OK).entity(order).build();
}
#GET
#Path("/{orderid}/products")
public ProductResource getProducts() {
return new ProductResource();
}
}
#Path("/")
public class ProductResource {
#GET
#Path("/{productid}")
#Produces(MediaType.APPLICATION_JSON)
public Response getProduct(#PathParam("orderid") String orderid, #PathParam("productid") String productid) throws JSONException {
Product product = db.getProduct(productid);
return Response.status(Status.OK).entity(product).build();
}
}
I get a successful output when I do this:
http://localhost:8080/testApp/api/orders/O101
I can see the collection of the products linked to the order in the output so I copied the id and tried this
http://localhost:8080/testApp/api/orders/O101/products/P101
But I always get a 404 error. Why? How can I solve this?
This is my config in the web.xml
<servlet-mapping>
<servlet-name>TestApp</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>
EDIT
Thank you so much for your answers. Woke up this morning tired to test it with no success.
I tried your suggestions, but still get 404.
#Path("/orders")
public class OrderResource {
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response getOrder(#PathParam("id") String orderid)
throws JSONException {
Order order = db.getOrder(orderid);
return Response.status(Status.OK).entity(order).build();
}
#GET
#Path("/{orderid}/products") //Here I added after products /{productID} which gives me an empty JSON. Never reach the method from the subresource.
public ProductResource getProducts() {
return new ProductResource();
}
}
public class ProductResource {
#Path("/{productid}") //Here I tried to remove the slash also.
#Produces(MediaType.APPLICATION_JSON)
public Response getProduct(#PathParam("orderid") String orderid, #PathParam("productid") String productid) throws JSONException {
Product product = db.getProduct(productid);
return Response.status(Status.OK).entity(product).build();
}
}
The problem is the #GET on the getProducts. A sub-resource locator is defined as a method with a #Path and which has no #METHOD. If you think about it, it makes perfect sense, as the there can be more than say just a #GET in the sub-resource class. So remove the #GET, and it should work. Leaving it would cause the method to not be a sub-resource locator, and it would behave like a normal resource method.
Aside from that, what others have suggested about the #Path("/") is not the cause of the problem, but it is a problem. What this does is cause Jersey to also register the ProductsResource as a root resource. So would be able to access /api/1234, since it is mapped to /. You probably don't want this. So you should remove the #Path("/") from the ProductsResource.
Sub-resources shouldn't be annotated with #Path on class level and they need to be registered with the JAX-RS runtinme.
Just remove the #Path annotation.
In your case, the problem seems to be the annotation #Path in your sub-resource. When defining a sub-resource, it should not be annotated at the class level with #Path. Also in your ProductResource, try removing the '/' from #Path("/{productid}") as it should be referenced from the context of the parent(OrderResource) and should not exists as an individual instance.
Thanks
Im' working on a spring application lately using hibernate, thymeleaf and spring security.
I stumbled upon a situation : i made a custom Exception :
#ResponseStatus(HttpStatus.BAD_REQUEST)
public class GlobalUrlParametersErrorsException extends RuntimeException {
private String errorMsgSignature;
private String errorMsgContent;
private String redirectionUrl;
public String getErrorMsgSignature() {
return errorMsgSignature;
}
public void setErrorMsgSignature(String errorMsgSignature) {
this.errorMsgSignature = errorMsgSignature;
}
public String getErrorMsgContent() {
return errorMsgContent;
}
public void setErrorMsgContent(String errorMsgContent) {
this.errorMsgContent = errorMsgContent;
}
public String getRedirectionUrl() {
return redirectionUrl;
}
public void setRedirectionUrl(String redirectionUrl) {
this.redirectionUrl = redirectionUrl;
}
public GlobalUrlParametersErrorsException(String errorMsgSignature, String errorMsgContent, String redirectionUrl){
this.errorMsgSignature = errorMsgSignature;
this.errorMsgContent = errorMsgContent;
this.redirectionUrl = redirectionUrl;
}
}
This exception is extended by some other custom exceptions i wrote.
To handle these exceptions i added a #ControllerAdvice :
#ControllerAdvice
public class GlobalDefaultExceptionHandler {
//some other exception like hibernate exception ...
#ExceptionHandler( value = GlobalUrlParametersErrorsException.class)//{InvalidDateException.class, InvalidRole.class, NoSuchUser.class, NoSuchNotification.class,NoSuchConsultation.class,NoSuchRdv.class})
public ModelAndView whenUrlDateParametersSuckMethod(RedirectAttributes redirectAttributes,GlobalUrlParametersErrorsException e){
redirectAttributes.addFlashAttribute(e.getErrorMsgSignature(),e.getErrorMsgContent());
return new ModelAndView("redirect:"+e.getRedirectionUrl());
}
#ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
ModelAndView m = new ModelAndView("errors/defaultError");
m.addObject("exception", e.toString());
m.addObject("url", req.getRequestURL());
return m;
}
}
But still my exceptions that i throw in the controller are not caught (i get glassfish error page)
So what's wrong with with my approch ?
PS: When i remove the exception handler of the my custom exception, and only let the other one , it works
Apparently you can't use RedirectAttributes in the method arguments of an #ExceptionHandler: the handler gets ignored.
Currently the docs say:
Much like standard controller methods annotated with a #RequestMapping annotation, the method arguments and return values of #ExceptionHandler methods can be flexible. For example, the HttpServletRequest can be accessed in Servlet environments and the PortletRequest in Portlet environments.
It looks like you could use the same parameters of a #RequestMapping method, but it doesn't work with RedirectAttributes.
A workaround is to forward to a #RequestMapping:
#ExceptionHandler(MyException.class)
public String handleMyException(MyException e) {
return "forward:/internalError";
}
#RequestMapping("/internalError")
public String internalError(RedirectAttributes redirectAttributes) {
// do stuff with redirectAttributes...
return "redirect:/someplace";
}
Try to add this line in your spring configuration:
<mvc:annotation-driven/>
If you don't put it, your class won't be loaded.
I have an AuthenticationController working fine: all of its methods are running and I can see the logged output of methods. After successful Authentication I return a new modal like this:
modelAndView = new ModelAndView("redirect:/home/");
.....
return modelAndView;
I have another controller named HomePageController, but, after returning a model from Authentication, I am not able to get the code execution in any method of HomePageController.
What mappings do I need?
#Controller
#RequestMapping(value = "/home")
#SessionAttributes({"loginModel"})
public class HomePageController extends AbstractController {
Note: All methods in AuthenticationController are working fine..
web.xml file : http://snipt.org/vgEd7
mct-serverlet.xml file: http://snipt.org/vgEf2
replace trailing space
modelAndView = new ModelAndView("redirect:/home");
.....
return modelAndView;
That will look for /home/index.htm or something.
and in your HomepgageController, make sure there is some method, which returns view for /home url.
#Controller
#RequestMapping(value = "/home")
#SessionAttributes({"loginModel"})
public class HomePageController extends AbstractController {
public string handleHomePage(){
return "View Name";
}
}
Try this return statement : return new ModelAndView("redirect:home");
Avoid / in "redirect:/home/" after home action. It will look for index action automatically.
For example, how to handle validation errors and possible exceptions in this controller action method:
#RequestMapping(method = POST)
#ResponseBody
public FooDto create(#Valid FooDTO fooDto, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return null; // what to do here?
// how to let the client know something has gone wrong?
} else {
fooDao.insertFoo(fooDto); // What to do if an exception gets thrown here?
// What to send back to the client?
return fooDto;
}
}
Throw an exception if you have an error, and then use #ExceptionHandler to annotate another method which will then handle the exception and render the appropriate response.
#RequestMapping(method = POST)
#ResponseBody
public FooDto create(#Valid FooDTO fooDto) {
//Do my business logic here
return fooDto;
}
Create a n exception handler:
#ExceptionHandler( MethodArgumentNotValidException.class)
#ResponseBody
#ResponseStatus(value = org.springframework.http.HttpStatus.BAD_REQUEST)
protected CustomExceptionResponse handleDMSRESTException(MethodArgumentNotValidException objException)
{
return formatException(objException);
}
I don't know if this is the correct approach i am following. I would appreciate if you could tell me what you have done for this issue.