In the notes for upgrading from Gradle 6.x, there’s a section that says this:
Since its inception, Gradle provided the
https://docs.gradle.org/current/userguide/upgrading_version_6.html#sec:configuration_removalcompile
andruntime
configurations to declare dependencies…
Theimplementation
configuration should [now] be used to declare dependencies which are implementation details of a library…
Theapi
configuration, available only if you apply thejava-library
plugin, should be used to declare dependencies which are part of the API of a library, that need to be exposed to consumers at compilation time.
In Gradle 7, both thecompile
andruntime
configurations are removed.
This got me wondering what impact making those changes actually has when authoring a Java library.
Gradle and dependencies
When Gradle is trying to build your application, it downloads dependencies from an artifact repository. More specifically for Java dependencies, it downloads a POM file and a JAR file. The POM file is XML and the JAR file is the build output from the Java compiler.
If you have a Java library with a few compile
dependencies, the JAR file built by Gradle when you do ./gradlew build
DOES NOT, by default, contain the JAR files for those dependencies. This wasn’t immediately obvious to me.
In order for Gradle to know about the compile
dependencies the POM file for the library must include references to the dependencies and that is how Gradle knows to download them as well and put them on the classpath too. This wasn’t immediately obvious to me either.
api vs implementation
So what’s the difference between the build output of a library when you use api
vs implementation
in the dependencies?
Nothing.
There’s only a difference if you generate a POM file to be stored in the artifact repository alongside your JAR, in which case you’ll see different XML output in the POM file. This wasn’t immediately obvious to me for the third time.
Here’s the Gradle dependencies config:
dependencies {
// This dependency is exported to consumers, that is to say found on their compile classpath.
api 'org.apache.commons:commons-math3:3.6.1'
// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation 'com.google.guava:guava:30.1.1-jre'
}
And here’s the resulting POM output when generated by the maven-publish plugin.
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jdbevan.gradle</groupId>
<artifactId>lib</artifactId>
<version>0.0.1</version>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>3.6.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1.1-jre</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
As you can see, api
dependencies get listed as compile
scope in the POM, and implementation
dependencies get listed as runtime
dependencies in the POM. Gradle uses this information to establish which classpath to make the dependencies available in: the compile classpath or the runtime one.
I guess the old dependency configurations used to just match the POM scopes exactly, and now they have different names, but the behaviour is still the same: the dependency configuration is used to create a POM file that defines which JAR files for which dependencies will be put on the classpath during compilation and at runtime.