Search code examples
spring-boottomcat9

Spring boot index page not mapped to / in Tomcat deployment


Yet another issue with deploying Spring Boot WAR to Tomcat... I have read the dozen of similar questions but have not found any fix for my issue.

I have a Spring Boot web app which is working fine when using the embedded tomcat web server (I can reach the index.html page using localhost:8080). However when I deploy the WAR to Tomcat (the war is called ROOT.war so am I deploying the app at Tomcat's root), localhost:8080 returns 404. I need to call localhost:8080/index.html to get an answer. I just cannot figure out why!

pom.xml

<modelVersion>4.0.0</modelVersion>

<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>war</packaging>

<properties>
    <java.version>11</java.version>
</properties>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.5</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    ...
</dependencies>

<build>
    <finalName>ROOT</finalName>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </pluginManagement>
    <plugins>
        <plugin>
            <groupId>com.github.eirslett</groupId>
            <artifactId>frontend-maven-plugin</artifactId>
            <version>1.12.1</version>
            <executions>
                ...
            </executions>
        </plugin>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
                <source>${java.version}</source>
                <target>${java.version}</target>
            </configuration>
        </plugin>
    </plugins>
</build>
</project>

Application.java

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    return application.sources(Application.class);
}
public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
}
}

I have one @RestController which does not override "/", and that's it. In the generated WAR, the index.html is located at the top level (so same level as WEB-INF). I also noticed that when Tomcat starts the web app, it prints:

INFO ServletWebServerApplicationContext ServletWebServerApplicationContext.prepareWebApplicationContext(ServletWebServerApplicationContext.java:292) [main] Root WebApplicationContext: initialization completed in 1674 ms
INFO WelcomePageHandlerMapping WelcomePageHandlerMapping.<init>(WelcomePageHandlerMapping.java:53) [main] Adding welcome page: ServletContext resource [/index.html]

I find the second line strange: it looks like Spring Boot is choosing to default back to a WelcomePageHandlerMapping instead of using the expected spring boot context. No idea where that comes from.

Maybe another indication: it does not print

Initializing Spring embedded WebApplicationContext

while this is printed when I start the app using the embedded Tomcat web server. But maybe it is fine if it is not there.

Tomcat version: 9.0.65 Tomcat config: default config: did not change anything there since installation.

Help!


Solution

  • I could reproduce!

    With:

    • Dockerfile:
      FROM tomcat:9.0.69-jre17-temurin-jammy
      ARG WAR_FILE=target/ROOT.war
      RUN addgroup --system tomcat \
         && adduser --system --ingroup tomcat tomcat \
         && chown -Rfh tomcat:tomcat $CATALINA_HOME
      USER tomcat:tomcat
      COPY ${WAR_FILE} $CATALINA_HOME/webapps/
      CMD ["catalina.sh", "run"]
      
    • pom.xml:
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
         <modelVersion>4.0.0</modelVersion>
         <parent>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-parent</artifactId>
          <!-- max spring boot version for tomcat 9 (servlet-api): -->
          <version>2.7.6</version> 
          <relativePath/> <!-- lookup parent from repository -->
         </parent>
         <groupId>com.example</groupId>
         <artifactId>traditional</artifactId>
         <version>0.0.1-SNAPSHOT</version>
         <packaging>war</packaging>
         <description>Demo project for Spring Boot</description>
         <properties>
          <java.version>17</java.version>
          <!-- latest tomcat9 version, property controls spring dependency management: -->
          <tomcat.version>9.0.69</tomcat.version> 
         </properties>
         <dependencies>
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
          </dependency>
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
          </dependency>
          <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
          </dependency>
         </dependencies>
         <build>
          <finalName>ROOT</finalName>
          <!-- no spring-boot plugin!(?) -->
         </build>
      </project>
      
    • App/Entry:
      package com.example.traditional;
      
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
      import org.springframework.boot.builder.SpringApplicationBuilder;
      import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
      
      @SpringBootApplication
      public class TraditionalApplication extends SpringBootServletInitializer {
         public static void main(String[] args) {
          SpringApplication.run(TraditionalApplication.class, args);
         }
         @Override
         protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
          return builder.sources(TraditionalApplication.class);
         }
      }
      
    • (Some /custom controller, (mockMvc) tests)
    • and a "static index.html" in src/main/webapp (maven war default):
      <html>
         <body>
           <h1>Hello</h1>
           Hello World!
         </body>
      </html>
      
    • the (embedded tomcat) test succeeds:
      @WebMvcTest
      public class WebTest {
      
         @Autowired
         MockMvc mockMvc;
      
         @Test
         void testRoot() throws Exception {
          mockMvc
             .perform(
                get("/")
             ).andExpectAll(
                status().isOk(),
                forwardedUrl("index.html")
             );
         } // ...
      }
      
    • but after:
      • mvn clean install \
      • && docker build -t my/tomcat9-app . \
      • && docker run -p 8080:8080 my/tomcat9-app,
    • we get:
      • 404 (tomcat error page) from http://localhost:8080
      • (http://localhost:8080/index.html, http://localhost:8080/custom work as expected ;(#

    Simplest Solution

    Move index.html from src/main/webapp to src/main/resources/static ! (stop running container, repeat mvn clean install && docker build ... && docker run);p #