Unable to resolve JSP page, whereas it's working fine with a simple string
Here is my folder structure of project
I am using following dep. to run an application
compile('org.apache.tomcat.embed:tomcat-embed-jasper:8.0.47')
#Configuration
#EnableWebMvc
#ComponentScan
public class SpringAppConfig extends WebMvcConfigurerAdapter implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(SpringAppConfig.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherContext = new AnnotationConfigWebApplicationContext();
dispatcherContext.register(SpringAppConfig.class);
ServletRegistration.Dynamic dispatcher = container.addServlet("dispatcher",
new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver internalResourceViewResolver = new InternalResourceViewResolver();
internalResourceViewResolver.setPrefix("/WEB-INF/jsp/");
internalResourceViewResolver.setSuffix(".jsp");
return internalResourceViewResolver;
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
#RestController
#RequestMapping("/app")
public class StudentController {
#RequestMapping("/hello") // this is working
public String sayHello() {
return "Hello from Spring 5 and embedded Tomcat 8!";
}
#RequestMapping(value = "/page", method = RequestMethod.GET)
public ModelAndView getPage() {
ModelAndView mav = new ModelAndView("welcome");
mav.addObject("firstname", "Amit");
mav.addObject("lastname", "Shah");
return mav;
}
}
getting 404 with following details when i hit http://localhost:8080/app/page
error: message /WEB-INF/jsp/welcome.jsp
description The requested resource is not available.
Tomacat
public class MyMvcApp {
private static final int PORT = 8080;
public static void main(String[] args) throws ServletException, LifecycleException {
String appBase = ".";
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir(createTempDir());
tomcat.setPort(PORT);
tomcat.getHost().setAppBase(appBase);
tomcat.addWebapp("", appBase);
tomcat.start();
tomcat.getServer().await();
}
private static String createTempDir() {
try {
File tempDir = File.createTempFile("tomcat.", "." + PORT);
tempDir.delete();
tempDir.mkdir();
tempDir.deleteOnExit();
return tempDir.getAbsolutePath();
} catch (IOException ex) {
throw new RuntimeException(
"Unable to create tempDir. java.io.tmpdir is set to " + System.getProperty("java.io.tmpdir"),
ex
);
}
}
}
I want to post http://localhost:8080/TestSpringMVCAndStuff/hi/zzz?input=abcdef&crap=1234567 to a /hi/* address.
However, I keep getting 404 error.
Is there a way to make this work?
WebAppInitializer
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(WebAppConfiguration.class);
ctx.setServletContext(container);
{
ServletRegistration.Dynamic servlet = container.addServlet(
"dispatcherHi", new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/hi/*");
}
}
}
AnotherController
#Controller
#RequestMapping("/hi/*")
public class AnotherController {
#Autowired(required=false)
#RequestMapping(method = RequestMethod.POST)
public void processRequest(HttpServletRequest request, HttpServletResponse response) {
try{
response.getOutputStream().write("hello world!".getBytes());
}
catch(Exception e){
e.printStackTrace();
}
}
}
WebAppConfiguration
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = "com.test.springmvc")
public class WebAppConfiguration extends WebMvcConfigurerAdapter {
/*
* Configure View Resolver
*/
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
/*
* Configure MessageSource to provide internationalized messages
*
*/
#Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("messages");
return messageSource;
}
/*
* Configure ResourceHandlers to serve static resources like CSS/ Javascript etc...
*
*/
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**").addResourceLocations("/static/");
}
}
In this Context Initializer servlet mapping is /hi/* so each and every request to the server will be like http://localhost:8080/TestSpringMVCAndStuff/hi
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(WebAppConfiguration.class);
ctx.setServletContext(container);
{
ServletRegistration.Dynamic servlet = container.addServlet(
"dispatcherHi", new DispatcherServlet(ctx));
servlet.setLoadOnStartup(1);
servlet.addMapping("/hi/*");
}
}
}
so that there is no need of #RequestMapping("/hi/*") this because /hi is already appended in context path so after hi/ only need a request Mapping #RequestMapping( value="/zzz")
#Controller
#RequestMapping("/**")
public class AnotherController {
#RequestMapping(value="/zzz",method = RequestMethod.POST)
#ResponseBody
public String processRequest(HttpServletRequest request, HttpServletResponse response) {
String result="";
try{
result="hello world";
}
catch(Exception e){
e.printStackTrace();
}
return result;
}
}
inCase he change servlet Mapping like this means servlet.addMapping("/") he should Map Request for /hi in Controller and futher request also
I'm trying to develop a spring MVC project which has a index.jsp file as it's view. I searched and found I should do this by the help of two below configuration classes:
public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
public static HashMap<String, String> response_code = new HashMap<String, String>();
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { MainConfiguration.class,
WebSocketConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
Security.addProvider(new BouncyCastleProvider());
servletContext.addListener(new MainContextListener());
}
}
and this is MainConfiguration class:
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = "-----")
public class MainConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/Content/**")
.addResourceLocations("/Content/");
registry.addResourceHandler("/Scripts/**")
.addResourceLocations("/Scripts/");
}
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver
= new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
I've configured my project to run on tomcat web-server inside intellij. but when index.jsp is directly in path : webapp/index.jsp it will be opened in browser after running the program but when I move it to a subfolder it doesn't.
Shouldn't this part of code give the address to spring so it find the url and don't give a 404 error?
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
You need to annotate your MainConfiguration.java with #Configuration. Also, you require to register an instance of the DispatcherServlet in the servlet context. Actually, you can refer - spring mvc with jsp example for detailed configurations.
As you know, in XML, the way to configure this is:
<error-page>
<error-code>404</error-code>
<location>/my-custom-page-not-found.html</location>
</error-page>
But I haven't found a way to do it in Java config. The first way I tried was:
#RequestMapping(value = "/**")
public String Error(){
return "error";
}
And it appeared to work, but it has conflicts retrieving the resources.
Is there a way to do it?
In Spring Framework, there are number of ways of handing exceptions (and particularly 404 error). Here is a documentation link.
First, you can still use error-page tag in web.xml, and customize error page. Here is an example.
Second, you can use one #ExceptionHandler for all controllers, like this:
#ControllerAdvice
public class ControllerAdvisor {
#ExceptionHandler(NoHandlerFoundException.class)
public String handle(Exception ex) {
return "404";//this is view name
}
}
For this to work, set throwExceptionIfNoHandlerFound property to true for DispatcherServlet in web.xml:
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
You can also pass some objects to error view, see javadoc for this.
The most clean solution since spring 4.2 RC3 is using the new createDispatcherServlet hook within the class extending AbstractDispatcherServletInitializer (or indirectly through extending AbstractAnnotationConfigDispatcherServletInitializer) like this:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
/* ... */
#Override
protected DispatcherServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
final DispatcherServlet dispatcherServlet = super.createDispatcherServlet(servletAppContext);
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
return dispatcherServlet;
}
}
Then you can use a global #ControllerAdvice (a class that is annotated with #ControllerAdvice) as described in the reference docs. Within the advice you can handle the NoHandlerFoundException with an #ExceptionHandler as described here.
This could look something like this:
#ControllerAdvice
public class NoHandlerFoundControllerAdvice {
#ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<String> handleNoHandlerFoundException(NoHandlerFoundException ex) {
// prepare responseEntity
return responseEntity;
}
}
Simple answer for 100% free xml:
Set properties for DispatcherServlet
public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { RootConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {AppConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
boolean done = registration.setInitParameter("throwExceptionIfNoHandlerFound", "true"); // -> true
if(!done) throw new RuntimeException();
}
}
Create #ControllerAdvice:
#ControllerAdvice
public class AdviceController {
#ExceptionHandler(NoHandlerFoundException.class)
public String handle(Exception ex) {
return "redirect:/404";
}
#RequestMapping(value = {"/404"}, method = RequestMethod.GET)
public String NotFoudPage() {
return "404";
}
}
Use code-based Servlet container initialization as described in the doc and override registerDispatcherServlet method to set throwExceptionIfNoHandlerFound property to true:
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() may not return empty or null");
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");
DispatcherServlet dispatcherServlet = new DispatcherServlet(servletAppContext);
// throw NoHandlerFoundException to Controller
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
customizeRegistration(registration);
}
}
Then create an exception handler:
#ControllerAdvice
public class ExceptionHandlerController {
#ExceptionHandler(Exception.class)
public String handleException(Exception e) {
return "404";// view name for 404 error
}
}
Don't forget about using #EnableWebMvc annotation on your Spring configuration file:
#Configuration
#EnableWebMvc
#ComponentScan(basePackages= {"org.project.etc"})
public class WebConfig extends WebMvcConfigurerAdapter {
...
}
In your web configuration class,
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter
Declare a bean as follows,
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container)
{
ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/401.html");
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404.html");
ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html");
container.addErrorPages(error401Page,error404Page,error500Page);
}
};
}
Add the mentioned html files(401.html .etc) to /src/main/resources/static/ folder.
Hope this helps
For Java config there is a method setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound) in DispatcherServlet. By settting it to true I guess you are doing same thing
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
then you can can this NoHandlerFoundException.class in controller advice as stated in above answer
it will be like something
public class WebXml implements WebApplicationInitializer{
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext context = getContext();
servletContext.addListener(new ContextLoaderListener(context));
DispatcherServlet dp = new DispatcherServlet(context);
dp.setThrowExceptionIfNoHandlerFound(true);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", dp);
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping(MAPPING_URL);
}
}
The solution proposed in comments above really works:
#Override
protected void customizeRegistration(ServletRegistration.Dynamic registration)
{
registration.setInitParameter("throwExceptionIfNoHandlerFound", "true");
}
A solution for Spring 5 and Thymeleaf 3.
In MyWebInitializer, enable exception throwing with setThrowExceptionIfNoHandlerFound(). We need to do casting to DispatcherServlet.
#Configuration
public class MyWebInitializer extends
AbstractAnnotationConfigDispatcherServletInitializer {
...
#Override
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
var dispatcher = (DispatcherServlet) super.createDispatcherServlet(servletAppContext);
dispatcher.setThrowExceptionIfNoHandlerFound(true);
return dispatcher;
}
}
Create a controller advice with #ControllerAdvice and add error message to the ModealAndView.
#ControllerAdvice
public class ControllerAdvisor {
#ExceptionHandler(NoHandlerFoundException.class)
public ModelAndView handle(Exception ex) {
var mv = new ModelAndView();
mv.addObject("message", ex.getMessage());
mv.setViewName("error/404");
return mv;
}
}
Create 404 error template, which displays the error message. Based on my configuration, the file is src/main/resources/templates/error/404.html.
<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>Resource not found</title>
</head>
<body>
<h2>404 - resource not found</h2>
<p>
<span th:text="${message}" th:remove="tag"></span>
</p>
</body>
</html>
For completeness, I add the Thymeleaf resolver configuration. We configure the Thymeleaf templates to be in templates directory on the classpath.
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {"com.zetcode"})
public class WebConfig implements WebMvcConfigurer {
#Autowired
private ApplicationContext applicationContext;
...
#Bean
public SpringResourceTemplateResolver templateResolver() {
var templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(applicationContext);
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
return templateResolver;
}
...
}
In springboot it is even simplier. Because of Spring autoconfiguration stuff, spring creates a bean org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties. This class is annotated with #ConfigurationProperties(prefix = "spring.mvc") and therefore it will seek for properties with spring.mvc prefix.
Part from javadoc:
Annotation for externalized configuration. Add this to a class definition or a
* #Bean method in a #Configuration class if you want to bind and validate
* some external Properties (e.g. from a .properties file).
You just have to add to your i.e. application.properties file following properties:
spring.mvc.throwExceptionIfNoHandlerFound=true
spring.resources.add-mappings=false //this is for spring so it won't return default handler for resources that not exist
and add exception resolver as follows:
#ControllerAdvice
public class ExceptionResponseStatusHandler {
#ExceptionHandler(NoHandlerFoundException.class)
public ModelAndView handle404() {
var out = new ModelAndView();
out.setViewName("404");//you must have view named i.e. 404.html
return out;
}
}
I have simple Spring MVC application where I want to hande 404 Not found exceptions in my Advice Controller class
Configuration:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class, SecurityConfig.class};
}
#Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new SessionListener());
FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("encodingFilter", new CharacterEncodingFilter());
encodingFilter.setInitParameter("encoding", "UTF-8");
encodingFilter.setInitParameter("forceEncoding", "true");
encodingFilter.addMappingForUrlPatterns(null, true, "/*");
}
}
Controller:
#Controller
#RequestMapping("/")
public class HomeController {
#RequestMapping(value = "/error/", method = RequestMethod.GET)
public String error(){return "error";}
}
ControllerAdvice:
#ControllerAdvice
public class AdviceController {
#ExceptionHandler(MyOwnException.class)
#ResponseStatus(value= HttpStatus.BAD_REQUEST)
public String checkoutException(CheckoutException e, HttpServletRequest httpServletRequest) {
return "error";
}
}
I can catch my own exceptions when I manually throw MyOwnException but I can't get how to catch NoHandlerFound exception. I need to send 404 error code and appropriate error.jsp page when there is no controller method to handle request
If your webapp is using web.xml it's very simple - just add the following (assuming usage of InternalResourceViewResolver with prefix pointing at your WEB-INF view folder and suffix .jsp). You can have multiple error-page elements of other error codes too.
<error-page>
<error-code>404</error-code>
<location>/error</location>
</error-page>
If you are not using web.xml it's more complicated and you'll have to define and register your own ExceptionResolver. Take a look at this spring.io blog article for details on how to do this.
(Edit after comment)
If you want to catch the NoHandlerFound exception you first have to tell Spring to throw it via setting a flag in the DispatcherServlet directly. To do so, in your AppInitializer class add the DispatcherServlet definition on top of what you are currently doing to add the flag:
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
servletContext.addListener(new SessionListener());
//BEGIN OF NEW CODE
WebApplicationContext context = getContext();
DispatcherServlet dispatcherServlet = new DispatcherServlet(context);
//we did all this to set the below flag
dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet",dispatcherServlet );
//END OF NEW CODE
FilterRegistration.Dynamic encodingFilter = servletContext.addFilter("encodingFilter", new CharacterEncodingFilter());
encodingFilter.setInitParameter("encoding", "UTF-8");
encodingFilter.setInitParameter("forceEncoding", "true");
encodingFilter.addMappingForUrlPatterns(null, true, "/*");
}
Then you can catch the NoHandlerFound exception directly in your AdviceController:
#ControllerAdvice
public class AdviceController {
//..
#ExceptionHandler(NoHandlerFoundException.class)
public String dealWithNoHandlerFoundException(CheckoutException e, HttpServletRequest httpServletRequest) {
return "error";
}
}