/java-ecosystem-capabilities

Gradle plugin that adds Capabilities to well-known components hosted on Maven Central.

Primary LanguageJavaApache License 2.0Apache-2.0

Java Ecosystem Capabilities Gradle Plugin

This plugin adds Capabilities to the metadata of well-known components hosted on Maven Central that are used in many Java projects.

What is a 'Capability' in Gradle and why should I care?

In the video below, I explain the concept of Capability Conflicts and why they can help you to avoid "dependency hell" in your project. With this plugin, you enable Gradle to detect and automatically resolved typical capability conflicts in the Java Ecosystem.

How to use the plugin?

Apply the plugin to all (sub)projects of your build so that the capability-adding rules are active everywhere. There is nothing else you need to do. The rules will now be considered when dependencies are resolved. For general information about how to structure Gradle builds, and apply community plugins like this one to all subprojects, you can check out my Understanding Gradle video series.

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("de.jjohannes.gradle:java-ecosystem-capabilities:0.4")
}

Apply the plugin

In your convention plugin, apply the plugin.

plugins {
    ...
    id("de.jjohannes.java-ecosystem-capabilities")
}

Alternative: Copy rules into your build logic

Instead of applying this plugin, you may also copy selected rules to your own build logic and register them in your convention plugin(s) directly. In the list below, all rule implementations are linked. Here they are implemented in Java, but converting them to Kotlin or Groovy should be straightforward if you prefer.

I use the plugin and now there is a conflict - what now?

The plugin configures Gradle to resolve conflicts by selecting the highest version. If this is not possible, because there is no single highest version, you will get a conflict error.

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
    }
  }
}

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 Jars with different names (e.g. jakarta.xml.bind-api-3.0.1.jar and jaxb-api-2.3.1.jar) but same/similar classes on the classpath. In this example, Gradle will use jakarta.xml.bind-api in place of jaxb-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.

Which Components does this plugin affect?

The following list shows all Capabilities and the Components they are added to. Each Capability's GA coordinates correspond to the GA coordinates of the Component that first introduced the Capability.

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

Please make sure, you clearly state which Capability it is about and which Components provide the Capability.

Special Case: Logging Libraries

This plugin does not contain rules for logging libraries, which is a specific area in which conflicts occur regularly. There is a separate plugin covering this topic by adding capabilities to the components of well-known logging APIs and implementations. Please apply that plugin in addition to this one:

plugins {
    ...
    id("de.jjohannes.java-ecosystem-capabilities")
    id("dev.jacomet.logging-capabilities")
}

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 {
            capability("${project.group}:${project.name}:${project.verson}") // keep default capability 'org.ow2.asm:asm'
            capability("asm:asm:${project.verson}")                          // add 'asm:asm'
        }
    }
    runtimeElements {
        outgoing {
            capability("${project.group}:${project.name}:${project.verson}") // keep default capability 'org.ow2.asm:asm'
            capability("asm:asm:${project.verson}")                          // add 'asm:asm'
        }
    }
}

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.2.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>

See also: Documentation of gradle-module-metadata-maven-plugin Maven Plugin