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!
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>
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);
}
}
/custom
controller, (mockMvc) tests)index.html
" in src/main/webapp
(maven war default):
<html>
<body>
<h1>Hello</h1>
Hello World!
</body>
</html>
@WebMvcTest
public class WebTest {
@Autowired
MockMvc mockMvc;
@Test
void testRoot() throws Exception {
mockMvc
.perform(
get("/")
).andExpectAll(
status().isOk(),
forwardedUrl("index.html")
);
} // ...
}
mvn clean install \
&& docker build -t my/tomcat9-app . \
&& docker run -p 8080:8080 my/tomcat9-app
,404
(tomcat error page) from http://localhost:8080
http://localhost:8080/index.html
, http://localhost:8080/custom
work as expected ;(#Move index.html
from src/main/webapp
to src/main/resources/static
! (stop running container, repeat mvn clean install && docker build ... && docker run
);p #