cyberangles blog

Classpath Conflict Between jetty-maven-plugin and tomcat-jdbc 8.0.9+: Resolving ServiceConfigurationError with JuliLog

As Java developers, we often rely on powerful tools like the Jetty Maven Plugin for rapid web application development and tomcat-jdbc for efficient database connection pooling. However, combining these tools can sometimes lead to frustrating classpath conflicts—especially when their transitive dependencies clash. One common issue is the ServiceConfigurationError related to JuliLog, a logging component used by Apache Tomcat.

In this blog, we’ll demystify this error, explore its root cause, and provide a step-by-step guide to resolve it. Whether you’re a seasoned developer or just starting with Maven, Jetty, or Tomcat, this post will help you diagnose and fix the conflict, ensuring your build and runtime environments work seamlessly.

2026-02

Table of Contents#

  1. Understanding the Problem: What is ServiceConfigurationError?
  2. Root Cause Analysis: Why Jetty and tomcat-jdbc Conflict
  3. Step-by-Step Resolution: Fixing the Classpath Conflict
  4. Verification: Ensuring the Error is Resolved
  5. Prevention Strategies: Avoiding Future Conflicts
  6. Conclusion
  7. References

1. Understanding the Problem: What is ServiceConfigurationError?#

Before diving into the conflict, let’s clarify what ServiceConfigurationError means. In Java, the Service Provider Interface (SPI) allows frameworks to define a contract (interface) and let third parties provide implementations. The ServiceLoader class dynamically discovers these implementations by reading configuration files in META-INF/services/ on the classpath.

A ServiceConfigurationError occurs when:

  • The SPI contract (e.g., org.apache.juli.logging.Log) is present, but no valid implementation is found.
  • Multiple conflicting implementations of the same SPI are present, causing ambiguity.

In our case, the error typically looks like this:

java.util.ServiceConfigurationError: org.apache.juli.logging.Log: Provider org.apache.juli.logging.impl.SLF4JLog not found

This error arises because the JVM’s ServiceLoader tries to load an implementation of org.apache.juli.logging.Log (used by tomcat-jdbc), but the specified provider class (e.g., SLF4JLog) is missing or incompatible with the classpath.

2. Root Cause Analysis: Why Jetty and tomcat-jdbc Conflict#

To resolve the conflict, we first need to understand why it happens. Let’s break down the dependencies:

tomcat-jdbc and Apache Juli Logging#

The tomcat-jdbc library (Apache Tomcat’s JDBC connection pool) depends on Apache Juli, Tomcat’s internal logging framework. Specifically, tomcat-jdbc includes tomcat-juli (via transitive dependency), which provides classes like org.apache.juli.logging.Log and its implementations (e.g., SLF4JLog, Jdk14Log).

In tomcat-jdbc 8.0.9+, the META-INF/services/org.apache.juli.logging.Log file (an SPI configuration) may list SLF4JLog as a provider. This assumes an SLF4J binding (e.g., slf4j-simple, logback-classic) is present to bridge Juli logs to SLF4J.

jetty-maven-plugin and Logging Dependencies#

The Jetty Maven Plugin (jetty-maven-plugin) is used to run web apps locally during development. Jetty itself relies on SLF4J for logging and may pull in transitive dependencies like:

  • slf4j-api (the SLF4J API).
  • An SLF4J binding (e.g., jetty-util-log or slf4j-jdk14).

If jetty-maven-plugin includes an older or conflicting version of SLF4J, or if it omits a required binding, tomcat-jdbc’s JuliLog SPI will fail to find the SLF4JLog implementation—triggering the ServiceConfigurationError.

The Conflict in Action#

The classpath ends up with:

  • tomcat-jdbc’s tomcat-juli (with SPI config for SLF4JLog).
  • Jetty’s logging dependencies (possibly an incompatible SLF4J version or missing binding).

Since SLF4JLog requires SLF4J API and a binding, a mismatch here leaves the SPI provider unresolvable, causing the error.

3. Step-by-Step Resolution: Fixing the Classpath Conflict#

Let’s resolve the conflict with three key steps: identifying dependencies, excluding conflicts, and aligning logging libraries.

3.1 Identify Conflicting Dependencies#

First, use Maven’s dependency:tree goal to visualize your project’s dependency tree and spot conflicting logging libraries. Run:

mvn dependency:tree -Dincludes=org.apache.juli,org.slf4j

This command filters the tree to show only org.apache.juli (Tomcat Juli) and org.slf4j (SLF4J) dependencies. You might see output like:

[INFO] +- org.apache.tomcat:tomcat-jdbc:jar:8.5.82:compile
[INFO] |  \- org.apache.tomcat:tomcat-juli:jar:8.5.82:compile
[INFO] +- org.eclipse.jetty:jetty-maven-plugin:jar:9.4.54.v20240208:plugin
[INFO] |  \- org.eclipse.jetty:jetty-util:jar:9.4.54.v20240208:plugin
[INFO] |     \- org.slf4j:slf4j-api:jar:1.7.36:plugin

Here, tomcat-juli (from tomcat-jdbc) and slf4j-api (from Jetty) are on the classpath. The conflict arises because tomcat-juli expects an SLF4J binding (e.g., slf4j-simple) to resolve SLF4JLog, but Jetty may not include it.

3.2 Exclude Transitive Conflicts#

The first step is to exclude conflicting transitive dependencies from either jetty-maven-plugin or tomcat-jdbc. Since tomcat-jdbc’s tomcat-juli is the source of the SPI config, we’ll exclude it and replace it with a compatible logging bridge.

Exclude tomcat-juli from tomcat-jdbc#

Update your pom.xml to exclude tomcat-juli from tomcat-jdbc (this removes the conflicting Juli logging classes):

<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jdbc</artifactId>
    <version>8.5.82</version> <!-- Use your tomcat-jdbc version -->
    <exclusions>
        <!-- Exclude Tomcat's Juli logging to avoid SPI conflicts -->
        <exclusion>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-juli</artifactId>
        </exclusion>
    </exclusions>
</dependency>

3.3 Align Logging Dependencies#

With tomcat-juli excluded, we need to ensure tomcat-jdbc can still log properly. We’ll use SLF4J bridges to route Tomcat’s Juli logs to SLF4J (which Jetty already uses).

Add SLF4J API and Binding#

Ensure your project includes a compatible SLF4J API and binding. If Jetty already brings in slf4j-api, add a binding like slf4j-simple (for simplicity) or logback-classic (for production):

<!-- SLF4J API (align with Jetty's version) -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.36</version> <!-- Match Jetty's SLF4J version -->
</dependency>
 
<!-- SLF4J binding (simple logger for development) -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>1.7.36</version>
    <scope>runtime</scope>
</dependency>

Bridge Juli Logs to SLF4J#

Tomcat’s Juli logging uses org.apache.juli.logging.Log, which isn’t natively compatible with SLF4J. Add the juli-slf4j-bridge to route Juli logs to SLF4J:

<!-- Bridge Apache Juli logs to SLF4J -->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-juli-slf4j-logback</artifactId>
    <version>10.1.0</version> <!-- Use a version compatible with your setup -->
</dependency>

Note: If using Tomcat 8.x, use tomcat-juli-slf4j instead (older versions).

Verify Jetty’s Logging Setup#

Ensure jetty-maven-plugin isn’t pulling in conflicting logging dependencies. Exclude any redundant SLF4J bindings from the plugin:

<plugin>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>9.4.54.v20240208</version> <!-- Use your Jetty version -->
    <dependencies>
        <!-- Exclude conflicting SLF4J bindings from Jetty -->
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-util</artifactId>
            <version>9.4.54.v20240208</version>
            <exclusions>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-reload4j</artifactId> <!-- Example: Exclude Reload4J if present -->
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
</plugin>

4. Verification: Ensuring the Error is Resolved#

After updating your pom.xml, verify the fix with these steps:

Step 1: Clean and Rebuild#

Run mvn clean install to rebuild the project and apply dependency changes.

Step 2: Check the Dependency Tree#

Re-run mvn dependency:tree -Dincludes=org.apache.juli,org.slf4j to confirm:

  • tomcat-juli is no longer present (excluded successfully).
  • Only one version of slf4j-api and a single binding (e.g., slf4j-simple) are present.

Step 3: Run with Jetty#

Start your app with mvn jetty:run and check the logs. If successful, you’ll see Jetty start without the ServiceConfigurationError.

Example success output:

[INFO] Started Jetty Server
[INFO] Starting scanner at interval of 10 seconds.

Step 4: Validate Logging#

Test database connectivity to ensure tomcat-jdbc works (e.g., run a query). Check logs for tomcat-jdbc messages (e.g., connection pool initialization) to confirm logging is working:

[SimpleLogger] INFO: org.apache.tomcat.jdbc.pool.ConnectionPool - Initializing connection pool...

5. Prevention Strategies: Avoiding Future Conflicts#

Classpath conflicts are common in Java, but you can minimize them with these practices:

Use dependency:tree Regularly#

Audit dependencies with mvn dependency:tree to identify transitive conflicts early. Tools like Maven Dependency Plugin help visualize the classpath.

Enforce Dependency Versions with dependencyManagement#

Use Maven’s <dependencyManagement> to lock versions of critical libraries (e.g., SLF4J, logging bindings) across your project:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.36</version> <!-- Enforce this version -->
        </dependency>
    </dependencies>
</dependencyManagement>

Avoid Redundant Logging Dependencies#

Logging frameworks (JUL, SLF4J, Log4j) are frequent conflict sources. Stick to one logging facade (e.g., SLF4J) and one binding (e.g., Logback) to reduce clutter.

Update Dependencies Proactively#

Outdated libraries are more likely to have unresolved conflicts. Use tools like Dependabot or Maven Versions Plugin to keep dependencies up to date.

6. Conclusion#

The ServiceConfigurationError between jetty-maven-plugin and tomcat-jdbc 8.0.9+ stems from conflicting logging dependencies—specifically, Apache Juli’s SPI configuration clashing with Jetty’s SLF4J setup. By excluding tomcat-juli, aligning SLF4J versions, and bridging Juli logs to SLF4J, you can resolve the conflict and restore a smooth development workflow.

Remember: classpath conflicts are solvable with careful dependency management. Use dependency:tree to diagnose issues, exclude redundant transitive dependencies, and align logging libraries to keep your build and runtime environments stable.

7. References#