The key difference between the standard Java plugin and the Java Library plugin is that the latter introduces the concept of an
API
exposed to consumers. A library is a Java component meant to be consumed by other components. It’s a very common use case in multi-project builds, but also as soon as you have external dependencies.
The plugin exposes two
configurations
that can be used to declare dependencies:
api
and
implementation
.
The
api
configuration should be used to declare dependencies which are exported by the library API, whereas the
implementation
configuration should be used to declare dependencies which are internal to the component.
Example 2. Declaring API and implementation dependencies
build.gradle
dependencies {
api 'org.apache.httpcomponents:httpclient:4.5.7'
implementation 'org.apache.commons:commons-lang3:3.5'
}
build.gradle.kts
dependencies {
api("org.apache.httpcomponents:httpclient:4.5.7")
implementation("org.apache.commons:commons-lang3:3.5")
}
Dependencies appearing in the
api
configurations will be transitively exposed to consumers of the library, and as such will appear on the compile classpath of consumers. Dependencies found in the
implementation
configuration will, on the other hand, not be exposed to consumers, and therefore not leak into the consumers' compile classpath. This comes with several benefits:
-
dependencies do not leak into the compile classpath of consumers anymore, so you will never accidentally depend on a transitive dependency
-
faster compilation thanks to reduced classpath size
-
less recompilations when implementation dependencies change: consumers would not need to be recompiled
-
cleaner publishing: when used in conjunction with the new
maven-publish
plugin, Java libraries produce POM files that distinguish exactly between what is required to compile against the library and what is required to use the library at runtime (in other words, don’t mix what is needed to compile the library itself and what is needed to compile against the library).
|
The
compile
configuration still exists but should not be used as it will not offer the guarantees that the
api
and
implementation
configurations provide.
|
If your build consumes a published module with POM metadata, the Java and Java Library plugins both honor api and implementation separation through the scopes used in the pom.
Meaning that the compile classpath only includes
compile
scoped dependencies, while the runtime classpath adds the
runtime
scoped dependencies as well.
This often does not have an effect on modules published with Maven, where the POM that defines the project is directly published as metadata.
There, the compile scope includes both dependencies that were required to compile the project (i.e. implementation dependencies) and dependencies required to compile against the published library (i.e. API dependencies).
For most published libraries, this means that all dependencies belong to the compile scope.
If you encounter such an issue with an existing library, you can consider a
component metadata rule
to fix the incorrect metadata in your build.
However, as mentioned above, if the library is published with Gradle, the produced POM file only puts
api
dependencies into the compile scope and the remaining
implementation
dependencies into the runtime scope.
If your build consumes modules with Ivy metadata, you might be able to activate api and implementation separation as described
here
if all modules follow a certain structure.
|
Separating compile and runtime scope of modules is active by default in Gradle 5.0+. In Gradle 4.6+, you need to activate it by adding
enableFeaturePreview('IMPROVED_POM_SUPPORT')
in
settings.gradle
.
|