The Java Library Plugin
The Java Library plugin expands the capabilities of the Java Plugin ( java ) by providing specific knowledge about Java libraries. In particular, a Java library exposes an API to consumers (i.e., other projects using the Java or the Java Library plugin). All the source sets, tasks and configurations exposed by the Java plugin are implicitly available when using this plugin.
Usage
To use the Java Library plugin, include the following in your build script:
API and implementation separation
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.
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 and runtime configurations have been removed with Gradle 7.0. Please refer to the upgrade guide how to migrate to implementation and api configurations`.
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 Maven compile scoped dependencies, while the runtime classpath adds the Maven 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.
Recognizing API and implementation dependencies
This section will help you identify API and Implementation dependencies in your code using simple rules of thumb. The first of these is:
This keeps the dependencies off of the consumer’s compilation classpath. In addition, the consumers will immediately fail to compile if any implementation types accidentally leak into the public API.
So when should you use the api configuration? An API dependency is one that contains at least one type that is exposed in the library binary interface, often referred to as its ABI (Application Binary Interface). This includes, but is not limited to:
- types used in super classes or interfaces
- types used in public method parameters, including generic parameter types (where public is something that is visible to compilers. I.e. , public, protected and package private members in the Java world)
- types used in public fields
- public annotation types
By contrast, any type that is used in the following list is irrelevant to the ABI, and therefore should be declared as an implementation dependency:
- types exclusively used in method bodies
- types exclusively used in private members
- types exclusively found in internal classes (future versions of Gradle will let you declare which packages belong to the public API)
The following class makes use of a couple of third-party libraries, one of which is exposed in the class’s public API and the other is only used internally. The import statements don’t help us determine which is which, so we have to look at the fields, constructors and methods instead:
Example: Making the difference between API and implementation
// The following types can appear anywhere in the code // but say nothing about API or implementation usage import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; public class HttpClientWrapper < private final HttpClient client; // private member: implementation details // HttpClient is used as a parameter of a public method // so "leaks" into the public API of this component public HttpClientWrapper(HttpClient client) < this.client = client; >// public methods belongs to your API public byte[] doRawGet(String url) < HttpGet request = new HttpGet(url); try < HttpEntity entity = doGet(request); ByteArrayOutputStream baos = new ByteArrayOutputStream(); entity.writeTo(baos); return baos.toByteArray(); >catch (Exception e) < ExceptionUtils.rethrow(e); // this dependency is internal only >finally < request.releaseConnection(); >return null; > // HttpGet and HttpEntity are used in a private method, so they don't belong to the API private HttpEntity doGet(HttpGet get) throws Exception < HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) < System.err.println("Method failed: " + response.getStatusLine()); >return response.getEntity(); > >
The public constructor of HttpClientWrapper uses HttpClient as a parameter, so it is exposed to consumers and therefore belongs to the API. Note that HttpGet and HttpEntity are used in the signature of a private method, and so they don’t count towards making HttpClient an API dependency.
On the other hand, the ExceptionUtils type, coming from the commons-lang library, is only used in a method body (not in its signature), so it’s an implementation dependency.
Therefore, we can deduce that httpclient is an API dependency, whereas commons-lang is an implementation dependency. This conclusion translates into the following declaration in the build script:
Gradle Plugin Development Plugin
The Java Gradle Plugin development plugin can be used to assist in the development of Gradle plugins. It automatically applies the Java Library ( java-library ) plugin, adds the gradleApi() dependency to the api configuration and performs validation of plugin metadata during jar task execution.
The plugin also integrates with TestKit, a library that aids in writing and executing functional tests for plugin code. It automatically adds the gradleTestKit() dependency to the testImplementation configuration and generates a plugin classpath manifest file consumed by a GradleRunner instance if found. Please refer to Automatic classpath injection with the Plugin Development Plugin for more on its usage, configuration options and samples.
Usage
To use the Java Gradle Plugin Development plugin, include the following in your build script:
Applying the plugin automatically applies the Java Library( java-library ) plugin and adds the gradleApi() dependency to the api configuration. It also adds some validations to the build.
The following validations are performed:
- There is a plugin descriptor defined for the plugin.
- The plugin descriptor contains an implementation-class property.
- The implementation-class property references a valid class file in the jar.
- Each property getter or the corresponding field must be annotated with a property annotation like @InputFile and @OutputDirectory . Properties that don’t participate in up-to-date checks should be annotated with @Internal .
Any failed validations will result in a warning message.
For each plugin you are developing, add an entry to the gradlePlugin <> script block:
The gradlePlugin <> block defines the plugins being built by the project including the id and implementationClass of the plugin. From this data about the plugins being developed, Gradle can automatically:
- Generate the plugin descriptor in the jar file’s META-INF directory.
- Configure the Plugin Marker Artifact publications (Maven or Ivy) for each plugin.
- Publish each plugin to the Gradle Plugin Portal (see Publishing Plugins to Gradle Plugin Portal for details), but only if the Plugin Publishing Plugin has also been applied.
Interactions
Some of the plugin’s behaviour depends on other, related plugins also being applied in your build, namely the Maven Publish ( maven-publish ) and Ivy Publish ( ivy-publish ) plugins.
Other plugins auto apply the Java Gradle Plugin, like the Plugin Publishing Plugin.
Maven Publish Plugin
When the Java Gradle Plugin ( java-gradle-plugin ) detects that the Maven Publish Plugin ( maven-publish ) is also applied by the build, it will automatically configure the following MavenPublications:
- a single «main» publication, named pluginMaven , based on the main Java component
- multiple «marker» publications (one for each plugin defined in the gradlePlugin <> block), named PluginMarkerMaven (for example in the above example it would be simplePluginPluginMarkerMaven )
This automatic configuration happens in a Project.afterEvaluate() block (so at the end of the build configuration phase), and only if these publications haven’t already been defined, so it’s possible to create and customise them during the earlier stages of build configuration.
Ivy Publish Plugin
When the Java Gradle Plugin( java-gradle-plugin ) detects that the Ivy Publish Plugin ( ivy-publish ) is also applied by the build, it will automatically configure the following IvyPublications:
- a single «main» publication, named pluginIvy , based on the main Java component
- multiple «marker» publications (one for each plugin defined in the gradlePlugin <> block), named PluginMarkerIvy (for example in the above example it would be simplePluginPluginMarkerIvy )
This automatic configuration happens in a Project.afterEvaluate() block (so at the end of the build configuration phase), and only if these publications haven’t already been defined, so it’s possible to create and customise them during the earlier stages of build configuration.
Plugin Publish Plugin
Starting from version 1.0.0, the Plugin Publish Plugin always auto-applies the Java Gradle Plugin ( java-gradle-plugin ) and the Maven Publish Plugin ( maven-publish ).