javaspring-bootmaventomcat

Java 21 spring-boot application running on IntelliJ is failing to run on Tomcat 10.x and 11.x


I have a multi-module Spring-Boot 3.4.2 application running with Java 21. The application is working fine when I run it on Intellij (entities persist, webservice responds well), but when I generate a war and try to run it on a standalone Tomcat server (10 and 11), the one webservice I created doesn't respond (the entities persist). What would be the problem? Is it on my configuration or on the Tomcat server?

Here is the pom.xml content (properties, dependencies and build ) :

    <properties>
        <spring-boot.version>3.4.2</spring-boot.version>
        <java.version>21</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web-services</artifactId>
            <version>${spring-boot.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </dependency>
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--PERSISTENCE-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>6.6.8.Final</version>
        </dependency>
        <dependency>
            <groupId>jakarta.persistence</groupId>
            <artifactId>jakarta.persistence-api</artifactId>
            <version>3.1.0</version> <!-- Jakarta Persistence API -->
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <version>42.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <mainClass>ca.project.projectApplication</mainClass>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.10.0</version>
                    <configuration>
                        <source>${java.version}</source>
                        <target>${java.version}</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.4.0</version>
                    <configuration>
                        <failOnMissingWebXml>false</failOnMissingWebXml>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

and here is the application-properties content :

# PostgreSQL Database Connection Configuration
spring.datasource.url=jdbc:postgresql://localhost:5432/project_db
spring.datasource.username=postgres
spring.datasource.password=****
spring.datasource.driver-class-name=org.postgresql.Driver

# Hibernate Configuration
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true


#Hikary Configuration
spring.datasource.hikari.connectionTimeout=20000
spring.datasource.hikari.idleTimeout=30000
spring.datasource.hikari.maxLifetime=2000000
spring.datasource.hikari.maximumPoolSize=10

logging.level.org.springframework.web=DEBUG
logging.level.org.springframework.boot=DEBUG

#Context
server.servlet.context-path=/project

#Port
server.port=8081

Here is the main class content :

@SpringBootApplication
@ComponentScan(basePackages = {"ca"})
@EntityScan(basePackages = {"ca"})
@EnableJpaRepositories(basePackages = {"ca"})
public class ProjectApplication extends SpringBootServletInitializer{
    private static final Logger log = LogManager.getLogger(ProjectApplication.class);
    public static void main(String[] args) {
        SpringApplication.run(ProjectApplication.class);
        log.info("Project backend application started.");
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(ProjectApplication.class);
    }
}

And the RestController :

@RestController
@RequestMapping("/")
public class HealthCheckController {

    @Autowired
    IHealthCheckService healthCheckService;

    @GetMapping("health-check")
    public String performHealthCheck(){
        boolean isHealthy = healthCheckService.testHealth();
        if(isHealthy){
            return "test ok";
        }
        return "test ko";
    }
}

So I am trying to call this : http://localhost:8081/project/health-check

Here is the log when I am running the WAR on a Tomcat server (10.x or 11.x)

2025-03-02 22:09:55.320  INFO DESKTOP-696GCGE --- [           main] c.c.ProjectApplication                    : Starting ProjectApplication v0.0.1-SNAPSHOT using Java 21.0.6 with PID 11188 (D:\Logiciels\apache-tomcat-10.1.34\webapps\Project-backend-mainapp-0.0.1-SNAPSHOT\WEB-INF\classes started by MIANGALY in D:\Logiciels\apache-tomcat-10.1.34\bin)
2025-03-02 22:09:55.345 DEBUG DESKTOP-696GCGE --- [           main] c.c.ProjectApplication                    : Running with Spring Boot v3.4.2, Spring v6.2.2
2025-03-02 22:09:55.349  INFO DESKTOP-696GCGE --- [           main] c.c.ProjectApplication                    : The following 1 profile is active: "dev"
2025-03-02 22:09:56.658  INFO DESKTOP-696GCGE --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2025-03-02 22:09:56.701  INFO DESKTOP-696GCGE --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 19 ms. Found 0 JPA repository interfaces.
2025-03-02 22:09:58.026  WARN DESKTOP-696GCGE --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.ws.config.annotation.DelegatingWsConfiguration' of type [org.springframework.ws.config.annotation.DelegatingWsConfiguration$$SpringCGLIB$$0] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying). The currently created BeanPostProcessor [annotationActionEndpointMapping] is declared through a non-static factory method on that class; consider declaring it as static instead.
2025-03-02 22:09:58.111  INFO DESKTOP-696GCGE --- [           main] .w.s.a.s.AnnotationActionEndpointMapping : Supporting [WS-Addressing August 2004, WS-Addressing 1.0]
2025-03-02 22:09:58.219  INFO DESKTOP-696GCGE --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2786 ms
2025-03-02 22:09:59.105  INFO DESKTOP-696GCGE --- [           main] o.h.j.i.u.LogHelper                      : HHH000204: Processing PersistenceUnitInfo [name: default]
2025-03-02 22:09:59.274  INFO DESKTOP-696GCGE --- [           main] o.h.Version                              : HHH000412: Hibernate ORM core version 6.6.8.Final
2025-03-02 22:09:59.391  INFO DESKTOP-696GCGE --- [           main] o.h.c.i.RegionFactoryInitiator           : HHH000026: Second-level cache disabled
2025-03-02 22:10:00.126  INFO DESKTOP-696GCGE --- [           main] o.s.o.j.p.SpringPersistenceUnitInfo      : No LoadTimeWeaver setup: ignoring JPA class transformer
2025-03-02 22:10:00.235  WARN DESKTOP-696GCGE --- [           main] c.z.h.HikariConfig                       : HikariPool-1 - idleTimeout has been set but has no effect because the pool is operating as a fixed size pool.
2025-03-02 22:10:00.237  INFO DESKTOP-696GCGE --- [           main] c.z.h.HikariDataSource                   : HikariPool-1 - Starting...
2025-03-02 22:10:01.034  INFO DESKTOP-696GCGE --- [           main] c.z.h.p.HikariPool                       : HikariPool-1 - Added connection org.postgresql.jdbc.PgConnection@1d4d84fb
2025-03-02 22:10:01.039  INFO DESKTOP-696GCGE --- [           main] c.z.h.HikariDataSource                   : HikariPool-1 - Start completed.
2025-03-02 22:10:01.209  WARN DESKTOP-696GCGE --- [           main] o.h.o.deprecation                        : HHH90000025: PostgreSQLDialect does not need to be specified explicitly using 'hibernate.dialect' (remove the property setting and it will be selected by default)
2025-03-02 22:10:01.279  INFO DESKTOP-696GCGE --- [           main] o.h.o.c.pooling                          : HHH10001005: Database info:
    Database JDBC URL [Connecting through datasource 'HikariDataSource (HikariPool-1)']
    Database driver: undefined/unknown
    Database version: 17.2
    Autocommit mode: undefined/unknown
    Isolation level: undefined/unknown
    Minimum pool size: undefined/unknown
    Maximum pool size: undefined/unknown
2025-03-02 22:10:03.969  INFO DESKTOP-696GCGE --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000489: No JTA platform available (set 'hibernate.transaction.jta.platform' to enable JTA platform integration)
2025-03-02 22:10:08.601  INFO DESKTOP-696GCGE --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2025-03-02 22:10:08.895  WARN DESKTOP-696GCGE --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2025-03-02 22:10:09.881  INFO DESKTOP-696GCGE --- [           main] c.c.ProjectApplication                    : Started ProjectApplication in 15.529 seconds (process running for 37.414)

The result :

Please note that :

I have tried running the war on Tomcat 10.1.34, 10.1.36 and 1.0.4. I have tried to add the "provided" scope to the tomcat dependency but it would fail even the Intellij run. I have tried removing the tomcat dependency, to no avail. I have tried changing the Tomcat server.xml by activating this :

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true"
               maxParameterCount="1000"
               >
        <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
        <SSLHostConfig>
            <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
                         certificateKeystorePassword="changeit" type="RSA" />
        </SSLHostConfig>
    </Connector>

I also tried to run only the code with the RestController, without the entities, but it still didn't work.


Solution

  • Quick answer:

    So if the test is deployed on Tomcat Server, the URL will be:

    http://localhost:8080/project/health-check

    application.properties

    The following two settings will not be used when deployed to Tomcat Server

    Tomcat Server runs on port 8080 by default.

    The context-path default is based on your war file name. If your war file name is hello.war, its context-path will be /hello. If your war file name is project.war, its context-path will be /project.

    pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
             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>
            <version>3.4.2</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>demo-springboot-web</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>war</packaging>
        <name>demo-springboot-web</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <maven.compiler.source>21</maven.compiler.source>
            <maven.compiler.target>21</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <exclusions>
                    <exclusion>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-tomcat</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    
            <dependency>
                <groupId>jakarta.platform</groupId>
                <artifactId>jakarta.jakartaee-api</artifactId>
                <version>10.0.0</version>
                <scope>provided</scope>
            </dependency>
            
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            
            <dependency>
                <groupId>org.postgresql</groupId>
                <artifactId>postgresql</artifactId>
                <scope>runtime</scope>
            </dependency>
            
        </dependencies>
    
        <build>
            <finalName>project</finalName>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    spring-boot-starter-web

    Because this war is to be deployed on Tomcat Server, I don't need Spring Boot to use embedded Tomcat, so I exclude the use of embedded Tomcat.

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <exclusions>
                    <exclusion>
                        <groupId>org.springframework.boot</groupId>
                        <artifactId>spring-boot-starter-tomcat</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
    

    jakartaee-api

            <dependency>
                <groupId>jakarta.platform</groupId>
                <artifactId>jakarta.jakartaee-api</artifactId>
                <version>10.0.0</version>
                <scope>provided</scope>
            </dependency>
    

    You do not need to include the following:

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </dependency>
            <dependency>
                <groupId>jakarta.servlet</groupId>
                <artifactId>jakarta.servlet-api</artifactId>
                <version>5.0.0</version>
            </dependency>
    

    JPA

    You just need to add the following two paragraphs.

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jpa</artifactId>
            </dependency>
            
            <dependency>
                <groupId>org.postgresql</groupId>
                <artifactId>postgresql</artifactId>
                <scope>runtime</scope>
            </dependency>
    

    You do not need to set the following:

            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-core</artifactId>
                <version>6.6.8.Final</version>
            </dependency>
            <dependency>
                <groupId>jakarta.persistence</groupId>
                <artifactId>jakarta.persistence-api</artifactId>
                <version>3.1.0</version> <!-- Jakarta Persistence API -->
            </dependency>
            <dependency>
                <groupId>org.postgresql</groupId>
                <artifactId>postgresql</artifactId>
                <version>42.7.5</version>
            </dependency>
    

    The postgresql version will be configured by spring-boot-starter-data-jpa. hibernate-core will also be configured by spring-boot-starter-data-jpa.

    Logging

    Spring Boot uses SLF4J plus Logback by default. Without any additional configuration (no logback.xml), you can use it directly in your program:

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    ...
    private static final Logger logger = LoggerFactory.getLogger(ProjectApplication.class);
    
    ...
    logger.info(">>> ProjectApplication init");
    ...
    

    ProjectApplication.java

    package com.example;
    
    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;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    @SpringBootApplication
    public class ProjectApplication extends SpringBootServletInitializer {
        private static final Logger logger = LoggerFactory.getLogger(ProjectApplication.class);
        public static void main(String[] args) {
            logger.info(">>> BEFORE - Project backend application started.");
            SpringApplication.run(ProjectApplication.class, args);
            logger.info(">>> AFTER - Project backend application started.");
        }
    
        public ProjectApplication(){
            logger.info(">>> ProjectApplication init");
        }
    
        @Override
        protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
            logger.info(">>> SpringBootServletInitializer configure");
            return application.sources(ProjectApplication.class);
        }
    }