1

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!

stackoverflowed
  • 686
  • 8
  • 22
  • https://stackoverflow.com/q/30972676/592355 , or just adding/fixing web.xml – xerx593 Dec 02 '22 at 10:36
  • @xerx593 I do have the setting you pasted in my Tomcat install web.xml file, but I do not have any web.xml file in my app. Isn't what SpringBootServletInitializer is supposed to do: configuring the web server context? – stackoverflowed Dec 02 '22 at 10:43
  • In particular, my appication.properties file does NOT contain any spring related config som server.servlet.context-path or spring.mvc.servlet.path, and I do not want to recourse to a web.xml file as the point of using Spring Boot is precisely to not need one. – stackoverflowed Dec 02 '22 at 12:30
  • @xerx593, I did follow the 3rd way (https://stackoverflow.com/a/33995679/4965515) and it worked, but it should not be needed to add a Controller to redirect from / to /index.html because of the default welcome-file mechanism defined in Tomcat's web.xml file. But thanks for the link, it helped. – stackoverflowed Dec 02 '22 at 13:14

1 Answers1

2

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 #

xerx593
  • 12,237
  • 5
  • 33
  • 64
  • I tried moving my html files til src/main/resources/static, but when doing so they appear in WEB-INF/classes/static in the WAR, and running the app in Tomcat doesn't work (error 404). Is there something to change in the POM config? – stackoverflowed Dec 05 '22 at 08:52
  • 404 (in that case) means; the (spring boot) app didn't load normally... Do you see "spring boot banner"/some spring log messages (out/logs) on tomcat start? – xerx593 Dec 05 '22 at 08:57
  • Indeed there was an issue with Tomcat. Now everything works without requiring the ugly "home controller hack". Thanks a lot fr the help :) – stackoverflowed Dec 05 '22 at 09:12
  • Cool, welcome, thx & was a pleasure to me! .. unfortunately the "explanation" is more complex than the "recommenation" ! (Spring (boot) static resource handling + external container...) – xerx593 Dec 05 '22 at 09:16