Table of Contents#
- Understanding the Problem: What is ServiceConfigurationError?
- Root Cause Analysis: Why Jetty and tomcat-jdbc Conflict
- Step-by-Step Resolution: Fixing the Classpath Conflict
- Verification: Ensuring the Error is Resolved
- Prevention Strategies: Avoiding Future Conflicts
- Conclusion
- 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 foundThis 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-logorslf4j-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’stomcat-juli(with SPI config forSLF4JLog).- 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.slf4jThis 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-juliis no longer present (excluded successfully).- Only one version of
slf4j-apiand 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.