JSP in Spring Boot application yields 404 Error no matter what I do

1.1k views Asked by At

I'll start off by saying I've looked at and tried the solutions in every question regarding this that I can find. The biggest problem is that most of these solutions are very old, and Spring Boot has changed a lot in the last several years. To be clear, I've tried this, this, this, this, and more. I've also read numerous tutorials. Nothing works.

I have a brand new Spring Boot application and I'm trying to get JSP rendering working with it. These are my dependencies:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>[2.3.4.RELEASE,3)</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
        <version>[2.3.4.RELEASE,3)</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
        <version>[2.3.4.RELEASE,3)</version>
    </dependency>

    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>

    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>[2.8.0,3)</version>
    </dependency>

    <dependency>
        <groupId>de.mkammerer</groupId>
        <artifactId>argon2-jvm</artifactId>
        <version>[2.7,3)</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>[8.0.21,9)</version>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.glassfish.jaxb</groupId>
        <artifactId>jaxb-runtime</artifactId>
        <version>[2.3.2,)</version>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>[2.3.4.RELEASE,3)</version>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <version>[9.0.38,)</version>
        <scope>runtime</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>[2.3.4.RELEASE,3)</version>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

My project is laid out as follows:

- source
  - production
    - java
      - [my source code packages]
    - resources
      - WEB-APP
        - jsp
          - initialization
            - begin.jsp
      - [my resource packages]
  - test
    - java
    - resources

"WEB-APP/jsp" is just the latest iteration I've tried. I've tried "WEB-INF/jsp", "META-INF/jsp", "webapp/jsp", no parent (just "jsp"), etc., all with the same results.

I know the parent directories are a bit non-standard, but it's configured correctly in Maven and I've confirmed it's not the source of my problems:

<build>
    <sourceDirectory>source/production/java</sourceDirectory>
    <resources>
        <resource>
            <directory>source/production/resources</directory>
        </resource>
    </resources>
    ...
</build>

My Application class is as follows:

@SpringBootApplication(scanBasePackages="com.my.project")
@EnableWebMvc
@EnableJpaRepositories("com.my.project.repository")
@EntityScan("com.my.project.model")
public class Application
{
    private static final Logger LOGGER = LogManager.getLogger(Application.class);

    public Application()
    {
    }

    @Bean
    public ViewResolver viewResolver()
    {
        LOGGER.info("Constructing InternalResourceViewResolver[JstlView]");
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-APP/jsp/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        resolver.setRedirectContextRelative(true);
        resolver.setRedirectHttp10Compatible(false);
        return resolver;
    }

    public static void main(final String[] args)
    {
        SpringApplication.run(Application.class, args);
    }
}

And my Controller:

@Controller
public class InitializationController
{
    private static final Logger LOGGER = LogManager.getLogger(InitializationController.class);

    @GetMapping("/initialize_application")
    public String beginInitialization(ModelMap model)
    {
        LOGGER.info("Beginning initialization");
        ...
        LOGGER.info("Returning view");
        return "initialization/begin";
    }
    ...
}

On startup I see the "Constructing InternalResourceViewResolver" log entry (my view resolver bean is created). When I go to /initialize_application, I get the following error:

Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.

Sun Oct 18 21:45:26 CDT 2020
There was an unexpected error (type=Not Found, status=404).

Looking in the log again, I see "Beginning initialization" and "Returning view," so I know that the 404 is for my JSP and not my controller. My controller is working.

Other things I've tried:

  • Initially I did not have @EnableWebMvc on my application. Without it, the log was empty except my log statements. When I added @EnableWebMvc, this is now logged with the 404: No mapping for GET /WEB-APP/jsp/initialization/begin.jsp (or whatever other directory I've tried other than "WEB-APP").
  • I've tried running this directly on the pure command line with mvn spring-boot:run
  • I've tried running this in IntelliJ IDEA with a Maven run configuration and command spring-boot:run (same result)
  • I've tried both <packaging>jar</packaging> and <packaging>war</packaging>, but neither make a difference, because neither a JAR nor a WAR are ever made. Maven runs the application directly out of the target/classes directory instead of creating an artifact.
  • When I've tried WEB-INF or META-INF instead of WEB-APP or webapp or something else, I've seen a logged warning: Path with "WEB-INF" or "META-INF": [WEB-INF/jsp/initialization/begin.jsp]

I have also confirmed that my JSPs are present in target/classes/WEB-APP/jsp (or whatever other directory I've tried other than "WEB-APP"), so they do exist.

I'm at a loss how to proceed. I'm beginning to think I need to ditch Spring Boot and stick with a traditional boilerplate Spring Web MVC application with a Servlet config and a Tomcat installation, but I was really excited about the "just runs" aspect of Spring Boot. Any help would be appreciated.

UPDATE 1

After reading this Spring documentation about JSP limitations, I now know that I have to use <packaging>war</packaging>, and I'm using that now, but it hasn't made a difference. I'm starting to suspect that the underlying problem here is that maven spring-boot:run doesn't create a WAR and run it, it just builds everything to target/classes and runs it from there.

Also, after finding this old, official Spring boot samples application, I've changed my project structure a little:

- source
  - production
    - java
      - [my source code packages]
    - resources
      - [my resource packages]
    - webapp
      - META-INF
      - WEB-INF
      - jsp
        - initialization
          - begin.jsp
  - test
    - java
    - resources

Updated my view resolver configuration:

resolver.setPrefix("/jsp/");
resolver.setSuffix(".jsp");

And added this to my POM:

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.3.1</version>
            <configuration>
                <warSourceDirectory>source/production/webapp</warSourceDirectory>
            </configuration>
        </plugin>

If I run mvn package, my WAR gets created correctly (classes and JSPs all where they should be), but neither mvn spring-boot:run nor mvn package spring-boot:run work—I still get 404 errors resolving my JSPs.

The old Spring Boot sample application linked to above puts the JSPs in WEB-INF/jsp, but I can't do that, because that results in the warning Path with "WEB-INF" or "META-INF": [WEB-INF/jsp/initialization/begin.jsp] (and still 404). What's frustrating is that this sample application doesn't exist anymore, nor does any new variation of it. I can't find any updated version that works with the newest version of Spring Boot. The sample application was deleted in 2.2.x.

2

There are 2 answers

14
wak786 On

Can you try by changing the scope of tomcat-embed-jasper to provided as this dependency is needed to compile JSPs.

    <dependency>
        <groupId>org.apache.tomcat.embed</groupId>
        <artifactId>tomcat-embed-jasper</artifactId>
        <version>[9.0.38,)</version>
        <scope>provided</scope>
    </dependency>

Edit:

I looked for various spring-boot + jsp projects over internet. I noticed that they have they also have spring-boot-starter-tomcat with provided scope. Can you try this.

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <version>[2.3.4.RELEASE,3)</version>
        <scope>provided</scope>
    </dependency>

References :

https://mkyong.com/spring-boot/spring-boot-hello-world-example-jsp/

https://dzone.com/articles/spring-boot-2-with-jsp-view

Edit-2 :

So this time i created a new springboot project. Did bare minimum setup to get jsp rendered. So basically i followed this tutorial and my project was running fine.

Then I replaced the pom.xml with yours and the i got the same error you mentioned in the question.

Then while doing trial and error i removed the <version>[9.0.38,)</version> from <artifactId>tomcat-embed-jasper</artifactId> and it started working for me.

        <!--I have removed version here and it started working for me-->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
<!--            <version>[9.0.38,)</version>-->
            <scope>provided</scope>
        </dependency>

Although i have different directory structure. But as you mentioned that is not the cause of issue.

I have uploaded the project to github. Feel free to pull it run it locally.

Github

0
M. Deinum On

Assuming the following location for your web content (which should be outside the classpath AFAIK) source/production/webapp. Spring Boot will ignore this due to a hardcoded path in DocumentRoot for detection of directories when running from the command-line or IDE (it will work when building a war and running that).

As a workaround you can add a TomcatContextCustomizer as a bean to detect the path and set it as the correct base.

package com.my.project;

@SpringBootApplication
public class Application extends SpringBootServletInitializer 
{
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) 
    {
        return application.sources(DemoApplication.class);
    }

    public static void main(final String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public TomcatContextCustomizer docBaseCustomizer() 
    {
        return new TomcatContextCustomizer() 
        {
            public void customize(Context context) 
            {
                File root = new File("source/production/webapp");
                if (root.exists() && root.isDirectory()) 
                {
                    context.setDocBase(root.getAbsolutePath());
                }        
            }
        }
    } 
}

Now add the following to your application.properties

spring.mvc.view.prefix=/jsp/
spring.mvc.view.suffix=.jsp

NOTE: The removal of the other annotations can only be done if your @SpringBootApplication annotated class is in the com.my.project package. It will then automatically detect the other classes (like entities and repositories).