javatomcatlog4jbuild.gradle

ClassNotFoundException: org.apache.logging.log4j.Logger with Tomcat10 and Jakarta


I have a Web Servlet (6.0) running on Tomcat 10.1 in Eclipse and Java 11 that I just migrated to Jakarta. At compile-time, everything is fine, but at runtime, Tomcat keeps giving me the following error:

java.lang.IllegalStateException: Error starting child
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:686)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:658)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:713)
    ... 37 more
Caused by: java.lang.NoClassDefFoundError: Lorg/apache/logging/log4j/Logger;
at java.base/java.lang.Class.getDeclaredFields0(Native Method)
at java.base/java.lang.Class.privateGetDeclaredFields(Class.java:3061)
... 38 more
Caused by: java.lang.ClassNotFoundException: org.apache.logging.log4j.Logger
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1437)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1245)
... 51 more

I have followed various approaches across stackoverflow, but to no avail.

I have the following dependencies in my build.gradle file:

plugins {
    id 'eclipse'
    id 'eclipse-wtp'
    id 'war'
    id 'application'
}

dependencies {

    compileOnly group: 'jakarta.servlet', name: 'jakarta.servlet-api', version: '6.1.0'
    
    implementation 'org.glassfish.jersey.core:jersey-server:3.1.7'
    implementation 'org.glassfish.jersey.media:jersey-media-json-jackson:3.1.7'
    implementation 'org.glassfish.jersey.containers:jersey-container-servlet:3.1.7'
    implementation group: 'org.glassfish.jersey.inject', name: 'jersey-hk2', version: '3.1.7'
    implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.14.0'
    
    implementation group: 'jakarta.ws.rs', name: 'jakarta.ws.rs-api', version: '4.0.0'
    
    implementation 'org.apache.logging.log4j:log4j-api:2.23.1'
    implementation 'org.apache.logging.log4j:log4j-core:2.23.1'
    implementation group: 'org.apache.logging.log4j', name: 'log4j-jakarta-web', version: '2.23.1'
    
    implementation "net.bull.javamelody:javamelody-core2129.0"
}

I also tried adding SLF4J2 dependencies as suggested in this answer, but that didn't help either:

implementation group: 'org.slf4j', name: 'slf4j-api', version: '2.0.13'
implementation group: 'org.apache.logging.log4j', name: 'log4j-slf4j2-impl', version: '2.23.1'
implementation group: 'org.slf4j', name: 'slf4j-reload4j', version: '2.0.13'

I tried adding the path to my log4j2.xml configuration file to context.xml as suggested in this answer, also without success:

<Context> 
<!-- ... -->
    <Parameter name="log4j2.configurationFile" value="../WEB-INF/log4j2.xml"/>

</Context>

Additionally, I

What can I do to further investigate or resolve this issue?


Solution

  • Minimum executable successful example code:

    environment:

    Project Directory tree

    ├── settings.gradle  (1)
    ├── build.gradle     (2)
    ├── gradle
    │   └── ...
    ├── gradlew
    ├── gradlew.bat
    └── src
        └── main
            ├── java
            │   └── com
            │       └── example
            │           └── MyServlet.java (3)
            ├── resources
            │   └── log4j2.xml (4)
            └── webapp (empty, no file)
                └── WEB-INF
    

    settings.gradle (1)

    rootProject.name = 'SimpleLog4j2Web'
    

    build.gradle (2)

    plugins {
        id 'java'
        id 'war'
    }
    
    group = 'org.example'
    version = '1.0-SNAPSHOT'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
    
        compileOnly 'jakarta.servlet:jakarta.servlet-api:6.0.0'
        compileOnly 'jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.0'
    
        // SLF4J API
        implementation 'org.slf4j:slf4j-api:2.0.7'
    
        // Log4j2 implementation for SLF4J
        implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0'
    
        // Log4j2 core
        implementation 'org.apache.logging.log4j:log4j-core:2.20.0'
    
        // Log4j2 API
        implementation 'org.apache.logging.log4j:log4j-api:2.20.0'
    
        testImplementation platform('org.junit:junit-bom:5.9.1')
        testImplementation 'org.junit.jupiter:junit-jupiter'
    }
    
    test {
        useJUnitPlatform()
    }
    
    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(17)
        }
    }
    
    // Set the name of the generated WAR file
    tasks.war {
        archiveFileName = 'simplelog4j2web.war'
    }
    
    
    tasks.withType(JavaCompile) {
        sourceCompatibility = '17'
        targetCompatibility = '17'
    }
    

    The jars related to logging only require the following four:

    // SLF4J API
    implementation 'org.slf4j:slf4j-api:2.0.7'
    
    // Log4j2 implementation for SLF4J
    implementation 'org.apache.logging.log4j:log4j-slf4j2-impl:2.20.0'
    
    // Log4j2 core
    implementation 'org.apache.logging.log4j:log4j-core:2.20.0'
    
    // Log4j2 API
    implementation 'org.apache.logging.log4j:log4j-api:2.20.0'
    

    MyServlet.java (3)

    package com.example;
    
    import jakarta.servlet.ServletException;
    import jakarta.servlet.annotation.WebServlet;
    import jakarta.servlet.http.HttpServlet;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.IOException;
    
    @WebServlet("/hello")
    public class MyServlet extends HttpServlet {
        private static final Logger logger = LoggerFactory.getLogger(MyServlet.class);
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            logger.info("Received request at /hello");
            resp.getWriter().write("Hello, Jakarta EE 10 with SLF4J and Log4j2!");
        }
    }
    

    log4j2.xml (4)

    <?xml version="1.0" encoding="UTF-8"?>
    <Configuration status="WARN">
        <Appenders>
            <!-- Console Appender Configuration -->
            <Console name="Console" target="SYSTEM_OUT">
                <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"/>
            </Console>
    
            <!-- File Appender Configuration -->
            <File name="File" fileName="../logs/app.log" append="true">
                <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"/>
            </File>
        </Appenders>
    
        <Loggers>
            <!-- Root Logger Configuration -->
            <Root level="info">
                <AppenderRef ref="Console"/>
                <AppenderRef ref="File"/>
            </Root>
    
            <!-- Application Specific Logger Configuration -->
            <Logger name="com.example" level="debug" additivity="false">
                <AppenderRef ref="Console"/>
                <AppenderRef ref="File"/>
            </Logger>
        </Loggers>
    </Configuration>
    

    Note that the output setting of fileName="../logs/app.log" takes Tomcat/bin as the starting point, so "../logs" must be added to output to the Tomcat/logs directory.

    fileName="../logs/app.log"
    

    build

    gradle clean build
    

    install war to tomcat

    copy build/libs/simplelog4j2web.war to Tomcat/webapps/

    startup tomcat

    cd Tomcat/bin
    ./startup.sh
    

    Test

    Browser open "http://localhost:8080/simplelog4j2web/hello"

    Check logs

    Tomcat/logs/

    Find app.log under Tomcat/logs

    Success write log.

    Tomcat war directory

    apache-tomcat-10.1.24/webapps/simplelog4j2web
    ├── META-INF
    │   ├── MANIFEST.MF
    │   └── war-tracker
    └── WEB-INF
        ├── classes
        │   ├── com
        │   │   └── example
        │   │       └── MyServlet.class
        │   └── log4j2.xml
        └── lib
            ├── log4j-api-2.20.0.jar
            ├── log4j-core-2.20.0.jar
            ├── log4j-slf4j2-impl-2.20.0.jar
            └── slf4j-api-2.0.7.jar
    

    Update - Logback - build.gradle

    plugins {
        id 'java'
        id 'war'
    }
    
    group = 'org.example'
    version = '1.0-SNAPSHOT'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
    
        compileOnly 'jakarta.servlet:jakarta.servlet-api:6.0.0'
        compileOnly 'jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.0'
    
        // SLF4J API    
        implementation 'org.slf4j:slf4j-api:2.0.13'
        
        // Logback Classic implementation (includes SLF4J binding)
        implementation 'ch.qos.logback:logback-classic:1.5.6'
    
        implementation 'ch.qos.logback:logback-core:1.5.6'
    
    
        testImplementation platform('org.junit:junit-bom:5.9.1')
        testImplementation 'org.junit.jupiter:junit-jupiter'
    }
    
    test {
        useJUnitPlatform()
    }
    
    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(17)
        }
    }
    
    // Set the name of the generated WAR file
    tasks.war {
        archiveFileName = 'simplelogbackweb.war'
    }
    
    tasks.withType(JavaCompile) {
        sourceCompatibility = '17'
        targetCompatibility = '17'
    }
    

    logback.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <!-- Console Appender Configuration -->
        <appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n</pattern>
            </encoder>
        </appender>
    
        <!-- File Appender Configuration -->
        <appender name="File" class="ch.qos.logback.core.FileAppender">
            <file>../logs/app.log</file>
            <append>true</append>
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n</pattern>
            </encoder>
        </appender>
    
        <!-- Root Logger Configuration -->
        <root level="info">
            <appender-ref ref="Console"/>
            <appender-ref ref="File"/>
        </root>
    
        <!-- Application Specific Logger Configuration -->
        <logger name="com.example" level="debug">
            <appender-ref ref="Console"/>
            <appender-ref ref="File"/>
        </logger>
    
    </configuration>
    

    Notice:

    Update

    Interesting to see what that would be with logback instead of log4j<<<

    I guess the interesting thing you want to express is this: logback output log, it will repeat the output twice.