These plugins add dependency capabilities to the metadata of well-known components hosted on Maven Central that are used in many Java projects. They also provide ways to resolve the potential capability conflict that can happen in the dependency graph of your application.
What is a dependency 'Capability' in Gradle and why should I care?
-
Ever seen this infamous Slf4J warning?
SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:.../slf4j-log4j12-1.7.29.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:.../logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
-
Ever wondered how to make sure all your dependencies' logging ends up in your Log4J 2 configured outputs?
-
Ever had to handle a
javax
tojakarta
migration and wondered how to make sure all your dependencies are compatible?
In the videos below, Jendrik explains the concept of Capability Conflicts and why they can help you to avoid "dependency hell" in your project.
ℹ️
|
With these plugins, you enable your build to detect and automatically resolve typical capability conflicts in the JVM ecosystem. |
Plugins
💡
|
These plugins require Gradle 6.8.3 at a minimum. |
The benefits described above are achieved through two plugins:
org.gradlex.jvm-dependency-conflict-detection
-
This plugin adds capability declarations to the metadata of well-known components hosted on Maven Central that are used in many Java projects. This plugin can be applied as a
Settings
or aProject
plugin. org.gradlex.jvm-dependency-conflict-resolution
-
This plugin adds opinionated capability resolutions to conflicting API or implementation libraries in the JVM ecosystem. This plugin can be applied as a
Project
plugin. It adds a DSL to configure resolution strategies, select logging implementations, and fix/enhance metadata of published components.
See each plugin’s documentation for more details.
JVM Dependency Conflict Detection
This plugin is responsible for applying metadata patching rules that add capability and alignment information to existing Java libraries.
⚠️
|
Applying only the jvm-dependency-conflict-detection plugin will result in broken builds if capability conflicts are found.
Conflict resolution is handled by the jvm-dependency-conflict-resolution plugin.
|
Plugin dependency
Add the following to the build file of your convention plugin’s build (e.g. build-logic/build.gradle(.kts)
or buildSrc/build.gradle(.kts)
).
Note that the jvm-dependency-conflict-resolution
dependency contains both the detection and the resolution plugins.
dependencies {
implementation("org.gradlex:jvm-dependency-conflict-resolution:2.1.2")
}
Apply the plugin
plugins {
id("org.gradlex.jvm-dependency-conflict-detection")
}
You can apply the plugin as a project plugin (build.gradle(.kts)
) or a setting plugin (settings.gradle(.kts)
).
If you don’t know what the difference is, using it as a project plugin (most Gradle plugins are project plugins) is the preferred way.
The following things are to consider:
-
If you use it as a project plugin make sure to apply it to all (sub)projects using a convention plugin
-
If you use it as a settings plugin you may directly apply it in your
settings.gradle(.kts)
-
If you write additional component metadata rules, or use other plugins that add more rules, make sure to consistently do either everything in projects or in settings as Gradle cannot combine both approaches.
-
What is the concrete effect of the plugin?
The plugin makes sure that during dependency resolution, you do not end up with two components that 'do the same thing' in the dependency resolution result.
That is, you won’t have two or more Jars with different names (e.g. jsr311-api-1.1.1.jar
, javax.ws.rs-api-2.1.1.jar
, jakarta.ws.rs-api-3.0.0.jar
and jaxrs-api-3.0.1.Final.jar
) but same/similar classes on the classpath.
In this example, Gradle will use jaxrs-api
in all places.
You can see all effects in this build scan
from this artificial sample project that includes dependencies to all components covered by rules in this plugin.
See Appendix for a full list of all capabilities that are added by this plugin and the components they apply to.
I use the plugin and now there is a conflict - what now?
💡
|
You can apply the jvm-dependency-conflict-resolution plugin to get some automatic conflict resolution. |
If you get an error like this:
> Module 'com.sun.mail:jakarta.mail' has been rejected:
Cannot select module with conflict on capability 'javax.mail:mail:2.0.1' also provided by [com.sun.mail:mailapi:2.0.1(compile)]
It means that you need to make a decision for the given capability - in this case javax.mail:mail
- by selecting one of the modules that both provide the capability.
In this case, you can decide between com.sun.mail:jakarta.mail
(see first line of message) and com.sun.mail:mailapi
(see end of second line).
A decision is made by defining a resolution strategy for the capability. This is best done in the place where you applied this plugin (e.g. one of your convention plugins):
configurations.all {
resolutionStrategy.capabilitiesResolution {
withCapability("javax.mail:mail") { // Capability for which to make the decision
select("com.sun.mail:jakarta.mail:0") // The component to select
}
}
}
One of the rules added by the plugin has an undesired effect - what now?
The goal of this plugin is to enrich the metadata of widely used components from Maven Central to allow Gradle to detect conflicts. The rules implemented in this plugin extend existing metadata with the mindset that the metadata should look like that in the first place. It just doesn’t for technical limitations during the development of the component. In most cases, because the component is published with Maven and only published POM metadata which cannot express capability information.
With that in mind, the rules should be usable as they are for almost all Gradle builds. If you encounter a problem with a rule in your build:
-
Maybe there is a mistake/bug in this plugin. Please open an issue to discuss it.
-
You might have a very special setup, where one of the rules causes trouble only in that setup.
In the second case, you cannot deactivate one of the rules in the plugin.
But you can treat the modified metadata as if it was the original metadata and add another rule on top to modify it further or to revert the effect of the rule in this plugin.
This can be expressed in a compact way if you also use the jvm-dependency-conflict-resolution
plugin. For example:
jvmDependencyConflicts {
patch.module("javax.xml.stream:stax-api") {
// Additional rule to revert the effect of the plugin on 'javax.xml.stream:stax-api'
removeCapability(CapabilityDefinition.STAX_API)
}
}
If you do not want to use the jvm-dependency-conflict-resolution
plugin, you can use Gradle’s general Component Metadata Rule API.
For example like this:
dependencies {
components.withModule("javax.xml.stream:stax-api") {
// Additional rule to revert the effect of the plugin on 'javax.xml.stream:stax-api'
allVariants {
withCapabilities {
removeCapability(
CapabilityDefinition.STAX_API.group,
CapabilityDefinition.STAX_API.capabilityName
)
}
}
}
}
Such additional rules are best added in the place where you applied this plugin (e.g. one of your convention plugins).
The snippet above shows how to add a rule without putting it into a separate class.
You can put it into a class (written in Java, Kotlin or Groovy) and use the @CacheableRule
annotation for better performance.
That’s how the rules in this plugin are implemented.
Consult the Gradle documentation on Component Metadata Rules for more details.
Something seems to be missing
This plugin collects rules that universally apply in the Java ecosystem. That means, that the information this plugin adds would ideally be already published in the metadata of the corresponding components. The idea is that every Java project can apply this plugin to avoid certain 'dependency hell' situations. Even if the project does not use any of the components this plugin affects directly, transitive dependency might bring in components that cause conflicts.
At the moment this plugin is only covering a fraction of the components on Maven Central that miss capability information. If you encounter more cases, please…
…contribute!
If you use this plugin and think it is missing a rule for a well-known component (or that a rule is incomplete/wrong), please let us know by
-
Providing a PR - for this you should look at the existing rules and follow the same patterns for new rules
Please make sure, you clearly state which Capability it is about and which Components provide the Capability.
I maintain a Component on Maven Central - How can I publish Capability information myself?
It would be great to see more components publishing capability information directly. If you wonder how you could do it, here is how:
Publishing with Gradle
Assuming the component you are publishing is org.ow2.asm:asm
.
You add the asm:asm
capability as follows:
configurations {
apiElements {
outgoing {
// keep default capability 'org.ow2.asm:asm'
capability("${project.group}:${project.name}:${project.version}")
// add 'asm:asm'
capability("asm:asm:${project.version}")
}
}
runtimeElements {
outgoing {
// keep default capability 'org.ow2.asm:asm'
capability("${project.group}:${project.name}:${project.version}")
// add 'asm:asm'
capability("asm:asm:${project.version}")
}
}
}
See also: Documentation in Gradle Manual
Publishing with Maven
Assuming the component you are publishing is org.ow2.asm:asm
.
You add the asm:asm
capability as follows:
<!-- do_not_remove: published-with-gradle-metadata -->
<build>
<plugins>
<plugin>
<groupId>de.jjohannes</groupId>
<artifactId>gradle-module-metadata-maven-plugin</artifactId>
<version>0.3.0</version>
<executions>
<execution>
<goals>
<goal>gmm</goal>
</goals>
</execution>
</executions>
<configuration>
<capabilities>
<capability>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
</capability>
</capabilities>
</configuration>
</plugin>
</plugins>
</build>
JVM Dependency Conflict Resolution
Plugin dependency
Add this to the build file of your convention plugin’s build (e.g. build-logic/build.gradle(.kts)
or buildSrc/build.gradle(.kts)
).
dependencies {
implementation("org.gradlex:jvm-dependency-conflict-resolution:2.1.2")
}
Apply the plugin
plugins {
id("org.gradlex.jvm-dependency-conflict-resolution")
}
This plugin should be applied to all projects, ideally as part of your convention plugin.
If the jvm-dependency-conflict-detection plugin has not been explicitly applied as well, either as a settings or project plugin, it will be automatically applied (as project plugin).
What is the concrete effect of the plugin?
The plugin registers capability conflict resolutions for the capabilities added by the jvm-dependency-conflict-plugin. This enables a number of capability conflicts to be resolved automatically. For conflicts that…
-
cannot be resolved automatically
-
should be resolved differently (than the default)
-
are not detected correctly due to incomplete metadata
…this plugin offers the jvmDependencyConflicts
DLS with the following sections.
jvmDependencyConflicts {
conflictResolution {
// Customize resolution of capability conflicts
select(JAVAX_ACTIVATION_API, "com.sun.activation:jakarta.activation")
}
logging {
// Customize resolution of conflicts for a certain logging setup
enforceSlf4JSimple()
}
patch {
// Patch or extend wrong metadata
module("com.googlecode.json-simple:json-simple") { removeDependency("junit:junit") }
}
consistentResolution {
// The runtime classpath of ':app' is always respected in version conflict detection and resolution
providesVersions(":app")
}
}
Customize resolution of capability conflicts
The plugin adds a default resolution strategy for each capability. Which in most cases means that Gradle will automatically pick the highest version of all components in conflict. This is to cover the cases where users just want things to work somehow. But it might not always be the right solution.
You can use the conflictResolution
section of jvmDependencyConflicts
to make an explicit selection for a given capability.
jvmDependencyConflicts {
conflictResolution {
// Explicitly select candidates for capabilities
select(CapabilityDefinition.CGLIB, "cglib:cglib")
select(CapabilityDefinition.JAVAX_MAIL_API, "com.sun.mail:jakarta.mail")
}
}
ℹ️
|
You may also use selectLenient() .
In a larger build, there can be situations where the desired selection is not always available.
For example, if the compile classpath of a module does not contain all dependencies of the final application runtime classpath.
In such cases, it may be okay and simplify things by just selecting one of the candidates, which selectLenient() does.
|
If you prefer to get a conflict reported without making a selection, you can also deactivate the default resolution strategy.
jvmDependencyConflicts {
conflictResolution {
// Deactivate default resolution strategy for selected rules
deactivateResolutionStrategy(CapabilityDefinitions.CGLIB)
deactivateResolutionStrategy(CapabilityDefinitions.JAVAX_MAIL_API)
}
}
The following table lists all available functionality:
Method | Documentation |
---|---|
|
Deactivate the default resolution strategy for a given capability. |
|
Select the highest available version if there is a conflict on the given capability. |
|
Select the given module if there is a conflict on the given capability. If the module is not part of the conflict: fail |
|
Select the given module if there is a conflict on the given capability. If the module is not part of the conflict: select first module found |
Select and enforce a logging framework
The logging
section of jvmDependencyConflicts
enables you to declaratively enforce the logging framework your application should use at build time.
💡
|
The different configuration options documented below do not add dependencies. Make sure to have the expected dependency in your graph, either as a direct or transitive one. |
The logging { }
section first provides a number of high-level, one stop solutions, for selecting a logging solution:
Method | Documentation | Required dependency |
---|---|---|
|
This will configure all capabilities to resolve in favour of LOGBack and route all alternative logging solutions through Slf4J. |
|
|
This will configure all capabilities to resolve in favour of Log4J 2 and route all alternative logging solutions through Log4J 2. |
|
|
This will configure all capabilities to resolve in favour of Slf4J simple and route all alternative logging solutions through Slf4J. |
|
💡
|
The method without parameter will apply the setup to all dependency configuration, while the other one will limit the setup to the specified dependency configuration. |
If you want a finer grained control, the logging { }
section provides lower level entry points for solving the different logging capability conflicts:
Method | Accepted parameter values | Documentation |
---|---|---|
|
Value must be an Slf4J binding implementation known by the plugin: |
Configures the provided Slf4J binding for selection, configuring related capabilities if needed |
|
A dependency configuration name, that |
Configures the provided Slf4J binding for selection, configuring related capabilities if needed, only for the provided dependency configuration |
|
Value must be a Log4J 1.2 implementation known by the plugin: |
Configures the provided Log4J 1.2 implementation for selection, configuring related capabilities if needed |
|
A dependency configuration name, that |
Configures the provided Log4J 1.2 implementation for selection, configuring related capabilities if needed, only for the provided dependency configuration |
|
Value must be a |
Configures the provided JUL integration of binding for selection, configuring related capabilities if needed |
|
A dependency configuration name, that |
Configures the provided JUL integration for selection, configuring related capabilities if needed, only for the provided dependency configuration |
|
Value must be a Apache Commons Logging interceptor or binding known by the plugin: |
Configures the provided commons logging interceptor or binding for selection, configuring related capabilities if needed |
|
A dependency configuration name, that |
Configures the provided commons logging interceptor or binding for selection, configuring related capabilities if needed, only for the provided dependency configuration |
|
Value must be a Log4J 2 module for Slf4J interaction known by the plugin: |
Configures the Log4J 2 / Slf4J integration, configuring related capabilities if needed |
|
A dependency configuration name, that |
Configures the Log4J 2 / Slf4J integration, configuring related capabilities if needed, only for the provided dependency configuration |
💡
|
Notations above are those accepted by DependencyHandler.create(notation) in Gradle that resolves to an ExternalDependency .
Most often this is a group:name:version String .
|
Patch metadata of published components
The patch
section of jvmDependencyConflicts
enables you to do individual adjustments to the metadata of published components.
This can be done to add information the jvm-dependency-conflict-detection plugin does not yet cover or to make opinionated adjustments for your context.
In the case of generally applicable adjustments, like adding a capability, please consider contributing your discovery back to the plugin by creating a PR.
jvmDependencyConflicts {
patch {
// patch metadata of the given module
module("io.netty:netty-common") {
// required adjustments (see table below)
}
// align versions (through BOM)
alignWithBom("org.ow2.asm:asm-bom", "org.ow2.asm:asm", "org.ow2.asm:asm-util")
// align versions (without using a BOM)
align("org.ow2.asm:asm", "org.ow2.asm:asm-util")
}
}
Method | Documentation |
---|---|
|
Add a dependency in 'api' scope (visible at runtime and compile time). |
|
Add a dependency in 'runtimeOnly' scope (visible at runtime). |
|
Add a dependency in 'compileOnlyApi' scope (visible at compile time). |
|
Remove the given dependency from all scopes. |
|
Reduce the given 'api' dependency to 'runtimeOnly' scope. |
|
Reduce the given 'api' dependency to 'compileOnlyApi' scope. |
|
Add a capability. |
|
Remove a capability. |
|
Make the Jar with the give 'classifier' known as Feature Variant so that it can be selected via capability in a dependency declaration. |
|
Make the Jar with the give 'classifier' known as additional variant with the OperatingSystemFamily and MachineArchitecture attributes set. |
|
Set the status of pre-release versions that are identified by one of the marker string (e.g. |
Configure global consistent resolution
The consistentResolution
section of jvmDependencyConflicts
allows to configure
consistent resolution
for all modules (subprojects) of your build.
By configuring which projects aggregate the final software product (applications or services that are delivered) you make sure
that the same versions of all third party dependencies you deliver as part of your product are also used when compiling and testing
parts of your softare (single subprojects) in isolation.
jvmDependencyConflicts {
consistentResolution {
// The runtime classpaths of the configured projects are always respected in
// version conflict detection and resolution
providesVersions(":app")
providesVersions(":service")
// If the build has a platform project, use it for additional version information
platform(":versions")
}
}
Method | Documentation |
---|---|
|
Respect runtime classpaths of given project in all version conflict detection and resolution. |
|
A platform/BOM to provide versions not available through consistent resolution alone. |
Appendix
Appendix A: All Capabilities
The following list shows all capabilities and the components they are added to.
Most capabilities use org.gradlex
as group and the name of the Component that first introduced the capability.
For capabilities that already exists because they are mentioned in published metadata (like com.google.collections:google-collections
) the official capability groups and names are used.
-
com.google.collections:google-collections
-
com.google.guava:listenablefuture
-
org.gradlex:aopalliance
-
org.gradlex:apache-csv
-
org.gradlex:asm
-
org.gradlex:bouncycastle-bcmail
-
org.gradlex:bouncycastle-bcpg
-
org.gradlex:bouncycastle-bcpkix
-
org.gradlex:bouncycastle-bcprov
-
org.gradlex:bouncycastle-bctls
-
org.gradlex:bouncycastle-bctsp
-
org.gradlex:bouncycastle-bcutil
-
org.gradlex:c3p0
-
org.gradlex:cglib
-
org.gradlex:commons-beanutils
-
org.gradlex:commons-io
-
org.gradlex:commons-logging-impl
-
org.gradlex:dom4j
-
org.gradlex:findbugs-annotations
-
org.gradlex:guava
-
org.gradlex:hamcrest-core
-
org.gradlex:hamcrest-library
-
org.gradlex:hikari-cp
-
org.gradlex:intellij-annotations
-
org.gradlex:jakarta-activation-api
-
org.gradlex:jakarta-activation-impl
-
org.gradlex:jakarta-annotation-api
-
org.gradlex:jakarta-json-api
-
org.gradlex:jakarta-mail-api
-
org.gradlex:jakarta-servlet-api
-
org.gradlex:jakarta-websocket-api
-
org.gradlex:jakarta-websocket-client-api
-
org.gradlex:jakarta-ws-rs-api
-
org.gradlex:java-assist
-
org.gradlex:javax-activation-api
-
org.gradlex:javax-annotation-api
-
org.gradlex:javax-ejb-api
-
org.gradlex:javax-el-api
-
org.gradlex:javax-inject-api
-
org.gradlex:javax-json-api
-
org.gradlex:javax-jws-api
-
org.gradlex:javax-mail-api
-
org.gradlex:javax-persistence-api
-
org.gradlex:javax-servlet-api
-
org.gradlex:javax-servlet-jsp
-
org.gradlex:javax-servlet-jstl
-
org.gradlex:javax-soap-api
-
org.gradlex:javax-transaction-api
-
org.gradlex:javax-validation-api
-
org.gradlex:javax-websocket-api-rule
-
org.gradlex:javax-ws-rs-api
-
org.gradlex:javax-xml-bind-api
-
org.gradlex:javax-xml-ws-api
-
org.gradlex:jcip-annotations
-
org.gradlex:jna-platform
-
org.gradlex:jts-core
-
org.gradlex:junit
-
org.gradlex:jzy3d-emul-gl
-
org.gradlex:jzy3d-jgl
-
org.gradlex:log4j2-impl
-
org.gradlex:log4j2-vs-slf4j
-
org.gradlex:lz4
-
org.gradlex:mchange-commons-java
-
org.gradlex:miglayout
-
org.gradlex:mysql-connector-java
-
org.gradlex:org-json
-
org.gradlex:postgresql
-
org.gradlex:slf4j-impl
-
org.gradlex:slf4j-vs-jcl
-
org.gradlex:slf4j-vs-jul
-
org.gradlex:slf4j-vs-log4j
-
org.gradlex:slf4j-vs-log4j2-for-jcl
-
org.gradlex:slf4j-vs-log4j2-for-jul
-
org.gradlex:slf4j-vs-log4j2-for-log4j
-
org.gradlex:stax-api
-
org.gradlex:velocity
-
org.gradlex:woodstox-asl
Appendix B: Capabilities for logging
The following lists the capabilities important for the logging topic (see logging DSL block).
Capability | Impacted modules | Comment |
---|---|---|
|
|
Represents an Slf4J binding |
|
|
Represents the native Log4J 2 implementation or delegation to Slf4J |
|
|
Represents the Slf4J / Log4J 2 relationship: which one delegates to the other |
|
|
Represents the Slf4J / Log4J 1.2 relationship: either Slf4J intercepts or binds to Log4J |
|
|
Represents the available Log4J implementation: native, with Slf4J or with Log4J 2 |
|
|
Represents the Slf4J / |
|
|
Represents JUL replacement: either with Slf4J or with Log4J 2 |
|
|
Represents Apache Commons Logging implementation: native or Slf4J |
|
|
Represents the Slf4J / Apache Commons Logging relationship: either Slf4J intercepts or binds to |
|
|
Represents the Slf4J or Log4J 2 interception of |
Appendix C: Alignment
In addition to the capability setting and conflict detection, the plugin also registers alignment rules for Slf4J and Log4J 2 modules.