I'm writing a Spring MVC application deployed on Tomcat. See the following minimal, complete, and verifiable example
public class Application extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { };
}
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { SpringServletConfig.class };
}
protected String[] getServletMappings() {
return new String[] { "/*" };
}
}
Where SpringServletConfig
is
@Configuration
@ComponentScan("com.example.controllers")
@EnableWebMvc
public class SpringServletConfig {
@Bean
public InternalResourceViewResolver resolver() {
InternalResourceViewResolver vr = new InternalResourceViewResolver();
vr.setPrefix("/WEB-INF/jsps/");
vr.setSuffix(".jsp");
return vr;
}
}
Finally, I have a @Controller
in the package com.example.controllers
@Controller
public class ExampleController {
@RequestMapping(path = "/home", method = RequestMethod.GET)
public String example() {
return "index";
}
}
My application's context name is Example
. When I send a request to
http://localhost:8080/Example/home
the application responds with an HTTP Status 404 and logs the following
WARN o.s.web.servlet.PageNotFound - No mapping found for HTTP request with URI `[/Example/WEB-INF/jsps/index.jsp]` in `DispatcherServlet` with name 'dispatcher'
I have a JSP resource at /WEB-INF/jsps/index.jsp
I expected Spring MVC to use my controller to handle the request and forward to the JSP, so why is it responding with a 404?
This is meant to be a canonical post for questions about this warning message.
Your standard Spring MVC application will serve all requests through a
DispatcherServlet
that you've registered with your Servlet container.The
DispatcherServlet
looks at itsApplicationContext
and, if available, theApplicationContext
registered with aContextLoaderListener
for special beans it needs to setup its request serving logic. These beans are described in the documentation.Arguably the most important, beans of type
HandlerMapping
mapThe javadoc of
HandlerMapping
further describes how implementations must behave.The
DispatcherServlet
finds all beans of this type and registers them in some order (can be customized). While serving a request, theDispatcherServlet
loops through theseHandlerMapping
objects and tests each of them withgetHandler
to find one that can handle the incoming request, represented as the standardHttpServletRequest
. As of 4.3.x, if it doesn't find any, it logs the warning that you seeand either throws a
NoHandlerFoundException
or immediately commits the response with a 404 Not Found status code.Why didn't the
DispatcherServlet
find aHandlerMapping
that could handle my request?The most common
HandlerMapping
implementation isRequestMappingHandlerMapping
, which handles registering@Controller
beans as handlers (really their@RequestMapping
annotated methods). You can either declare a bean of this type yourself (with@Bean
or<bean>
or other mechanism) or you can use the built-in options. These are:@Configuration
class with@EnableWebMvc
.<mvc:annotation-driven />
member in your XML configuration.As the link above describes, both of these will register a
RequestMappingHandlerMapping
bean (and a bunch of other stuff). However, aHandlerMapping
isn't very useful without a handler.RequestMappingHandlerMapping
expects some@Controller
beans so you need to declare those too, through@Bean
methods in a Java configuration or<bean>
declarations in an XML configuration or through component scanning of@Controller
annotated classes in either. Make sure these beans are present.If you're getting the warning message and a 404 and you've configured all of the above correctly, then you're sending your request to the wrong URI, one that isn't handled by a detected
@RequestMapping
annotated handler method.The
spring-webmvc
library offers other built-inHandlerMapping
implementations. For example,BeanNameUrlHandlerMapping
mapsand you can always write your own. Obviously, you'll have to make sure the request you're sending matches at least one of the registered
HandlerMapping
object's handlers.If you don't implicitly or explicitly register any
HandlerMapping
beans (or ifdetectAllHandlerMappings
istrue
), theDispatcherServlet
registers some defaults. These are defined inDispatcherServlet.properties
in the same package as theDispatcherServlet
class. They areBeanNameUrlHandlerMapping
andDefaultAnnotationHandlerMapping
(which is similar toRequestMappingHandlerMapping
but deprecated).Debugging
Spring MVC will log handlers registered through
RequestMappingHandlerMapping
. For example, a@Controller
likewill log the following at INFO level
This describes the mapping registered. When you see the warning that no handler was found, compare the URI in the message to the mapping listed here. All the restrictions specified in the
@RequestMapping
must match for Spring MVC to select the handler.Other
HandlerMapping
implementations log their own statements that should hint to their mappings and their corresponding handlers.Similarly, enable Spring logging at DEBUG level to see which beans Spring registers. It should report which annotated classes it finds, which packages it scans, and which beans it initializes. If the ones you expected aren't present, then review your
ApplicationContext
configuration.Other common mistakes
A
DispatcherServlet
is just a typical Java EEServlet
. You register it with your typical<web.xml>
<servlet-class>
and<servlet-mapping>
declaration, or directly throughServletContext#addServlet
in aWebApplicationInitializer
, or with whatever mechanism Spring boot uses. As such, you must rely on the url mapping logic specified in the Servlet specification, see Chapter 12. See alsoWith that in mind, a common mistake is to register the
DispatcherServlet
with a url mapping of/*
, returning a view name from a@RequestMapping
handler method, and expecting a JSP to be rendered. For example, consider a handler method likewith an
InternalResourceViewResolver
you might expect the request to be forwarded to a JSP resource at the path
/WEB-INF/jsps/example-view-name.jsp
. This won't happen. Instead, assuming a context name ofExample
, theDisaptcherServlet
will reportBecause the
DispatcherServlet
is mapped to/*
and/*
matches everything (except exact matches, which have higher priority), theDispatcherServlet
would be chosen to handle theforward
from theJstlView
(returned by theInternalResourceViewResolver
). In almost every case, theDispatcherServlet
will not be configured to handle such a request.Instead, in this simplistic case, you should register the
DispatcherServlet
to/
, marking it as the default servlet. The default servlet is the last match for a request. This will allow your typical servlet container to chose an internal Servlet implementation, mapped to*.jsp
, to handle the JSP resource (for example, Tomcat hasJspServlet
), before trying with the default servlet.That's what you're seeing in your example.
I resolved my issue when in addition to described before:`
added tomcat-embed-jasper:
` from: JSP file not rendering in Spring Boot web application
In my case, I was following the Interceptors Spring documentation for version 5.1.2 (while using Spring Boot v2.0.4.RELEASE) and the
WebConfig
class had the annotation@EnableWebMvc
, which seemed to be conflicting with something else in my application that was preventing my static assets from being resolved correctly (i.e. no CSS or JS files were being returned to the client).After trying a lot of different things, I tried removing the
@EnableWebMvc
and it worked!Edit: Here's the reference documentation that says you should remove the
@EnableWebMvc
annotationApparently in my case at least, I'm already configuring my Spring application (although not by using
web.xml
or any other static file, it's definitely programmatically), so it was a conflict there.I came across another reason for the same error. This could also be due to the class files not generated for your controller.java file. As a result of which the the dispatcher servlet mentioned in web.xml is unable to map it to the appropriate method in the controller class.
In eclipse under Project->select clean ->Build Project.Do give a check if the class file has been generated for the controller file under builds in your workspace.