Search code examples
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

  • restarted the Tomcat server
  • cleaned the work directory
  • checked that I don't have any old JARs in my classpath conflicting with gradle.

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


Solution

  • Minimum executable successful example code:

    environment:

    • JDK 17.0.11 (Eclipse Adoptium 17.0.11+9)
    • Gradle 8.7
    • Tomcat 10.1.24

    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/

    • app.log
    • localhost.2024-06-28.log
    • catalina.2024-06-28.log
    • localhost_access_log.2024-06-28.txt
    • catalina.out

    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:

    • (1) I have not set web.xml
    • (2) If the web.xml setting is incorrect, it may also cause other errors.
    • (3) When debugging, try to only test the parts that need to be tested, and do not add other unnecessary parts.

    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.