Movatterモバイル変換


[0]ホーム

URL:


The Java Library Plugin

version 8.14.3

The Java Library plugin expands the capabilities of theJava 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:

build.gradle.kts
plugins {    `java-library`}
build.gradle
plugins {    id 'java-library'}

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 anAPI 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 twoconfigurations that can be used to declare dependencies:api andimplementation.Theapi configuration should be used to declare dependencies which are exported by the library API, whereas theimplementation configuration should be used to declare dependencies which are internal to the component.

build.gradle.kts
dependencies {    api("org.apache.httpcomponents:httpclient:4.5.7")    implementation("org.apache.commons:commons-lang3:3.5")}
build.gradle
dependencies {    api 'org.apache.httpcomponents:httpclient:4.5.7'    implementation 'org.apache.commons:commons-lang3:3.5'}

Dependencies appearing in theapi configurations will be transitively exposed to consumers of the library, and as such will appear on the compile classpath of consumers. Dependencies found in theimplementation 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 newmaven-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).

Thecompile andruntime configurations have been removed with Gradle 7.0.Please refer to theupgrade guide how to migrate toimplementation andapi 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 Mavencompile scoped dependencies, while the runtime classpath adds the Mavenruntime 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 acomponent 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 putsapi dependencies into the compile scope and the remainingimplementation dependencies into the runtime scope.

If your build consumes modules with Ivy metadata, you might be able to activate api and implementation separation as describedhere 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 addingenableFeaturePreview('IMPROVED_POM_SUPPORT') insettings.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:

  • Prefer theimplementation configuration overapi when possible

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 theapi 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 (wherepublic is something that is visible to compilers. I.e. ,public,protected andpackage 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 animplementation 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

src/main/java/org/gradle/HttpClientWrapper.java
// The following types can appear anywhere in the code// but say nothing about API or implementation usageimport 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();    }}

Thepublic constructor ofHttpClientWrapper usesHttpClient as a parameter, so it is exposed to consumers and therefore belongs to the API. Note thatHttpGet andHttpEntity are used in the signature of aprivate method, and so they don’t count towards making HttpClient an API dependency.

On the other hand, theExceptionUtils type, coming from thecommons-lang library, is only used in a method body (not in its signature), so it’s an implementation dependency.

Therefore, we can deduce thathttpclient is an API dependency, whereascommons-lang is an implementation dependency. This conclusion translates into the following declaration in the build script:

build.gradle.kts
dependencies {    api("org.apache.httpcomponents:httpclient:4.5.7")    implementation("org.apache.commons:commons-lang3:3.5")}
build.gradle
dependencies {    api 'org.apache.httpcomponents:httpclient:4.5.7'    implementation 'org.apache.commons:commons-lang3:3.5'}

The Java Library plugin configurations

The following graph describes how configurations are setup when the Java Library plugin is in use.

java library ignore deprecated main
  • The configurations ingreen are the ones a user should use to declare dependencies

  • The configurations inpink are the ones used when a component compiles, or runs against the library

  • The configurations inblue are internal to the component, for its own use

And the next graph describes the test configurations setup:

java library ignore deprecated test

The role of each configuration is described in the following tables:

Table 1. Java Library plugin - configurations used to declare dependencies
Configuration nameRoleConsumable?Resolvable?Description

annotationProcessor

Declaring annotation processors

no

yes

This configuration is used to declare annotation processors, ensuring they are available during the compile phase for code generation.

api

Declaring API dependencies

no

no

This is where you declare dependencies which are transitively exported to consumers, for compile time and runtime.

implementation

Declaring implementation dependencies

no

no

This is where you declare dependencies which are purely internal and not meant to be exposed to consumers (they are still exposed to consumers at runtime).

compileOnly

Declaring compile only dependencies

no

no

This is where you declare dependencies which are required at compile time, but not at runtime. This typically includes dependencies which are shaded when found at runtime.

compileOnlyApi

Declaring compile only API dependencies

no

no

This is where you declare dependencies which are required at compile time by your module and consumers, but not at runtime. This typically includes dependencies which are shaded when found at runtime.

runtimeOnly

Declaring runtime dependencies

no

no

This is where you declare dependencies which are only required at runtime, and not at compile time.

testImplementation

Test dependencies

no

no

This is where you declare dependencies which are used to compile tests.

testCompileOnly

Declaring test compile only dependencies

no

no

This is where you declare dependencies which are only required at test compile time, but should not leak into the runtime. This typically includes dependencies which are shaded when found at runtime.

testRuntimeOnly

Declaring test runtime dependencies

no

no

This is where you declare dependencies which are only required at test runtime, and not at test compile time.

Table 2. Java Library plugin — configurations used by consumers
Configuration nameRoleConsumable?Resolvable?Description

apiElements

For compiling against this library

yes

no

This configuration is meant to be used by consumers, to retrieve all the elements necessary to compile against this library.

runtimeElements

For executing this library

yes

no

This configuration is meant to be used by consumers, to retrieve all the elements necessary to run against this library.

Table 3. Java Library plugin - configurations used by the library itself
Configuration nameRoleConsumable?Resolvable?Description

compileClasspath

For compiling this library

no

yes

This configuration contains the compile classpath of this library, and is therefore used when invoking the java compiler to compile it.

runtimeClasspath

For executing this library

no

yes

This configuration contains the runtime classpath of this library

testCompileClasspath

For compiling the tests of this library

no

yes

This configuration contains the test compile classpath of this library.

testRuntimeClasspath

For executing tests of this library

no

yes

This configuration contains the test runtime classpath of this library

Building Modules for the Java Module System

Since Java 9, Java itself offers amodule system that allows for strict encapsulation during compile and runtime.You can turn a Java library into aJava Module by creating amodule-info.java file in themain/java source folder.

src└── main    └── java        └── module-info.java

In the module info file, you declare amodule name, which packages of your module you want toexport and which other modules yourequire.

module-info.java file
module org.gradle.sample {    requires com.google.gson;          // real module    requires org.apache.commons.lang3; // automatic module    // commons-cli-1.4.jar is not a module and cannot be required}

To tell the Java compiler that a Jar is a module, as opposed to a traditional Java library, Gradle needs to place it on the so calledmodule path.It is an alternative to theclasspath, which is the traditional way to tell the compiler about compiled dependencies.Gradle will automatically put a Jar of your dependencies on the module path, instead of the classpath, if these three things are true:

  • java.modularity.inferModulePath isnot turned off

  • We are actually building a module (as opposed to a traditional library) which we expressed by adding themodule-info.java file.(Another option is to add theAutomatic-Module-Name Jar manifest attribute asdescribed further down.)

  • The Jar our module depends on is itself a module, which Gradles decides based on the presence of amodule-info.class — the compiled version of the module descriptor — in the Jar.(Or, alternatively, the presence of anAutomatic-Module-Name attribute the Jar manifest)

In the following, some more details about defining Java modules and how that interacts with Gradle’s dependency management are described.You can also look at aready made example to try out the Java Module support directly.

Declaring module dependencies

There is a direct relationship to the dependencies you declare in the build file and the module dependencies you declare in themodule-info.java file.Ideally the declarations should be in sync as seen in the following table.

Table 4. Mapping between Java module directives and Gradle configurations to declare dependencies
Java Module DirectiveGradle ConfigurationPurpose

requires

implementation

Declaring implementation dependencies

requires transitive

api

Declaring API dependencies

requires static

compileOnly

Declaring compile only dependencies

requires static transitive

compileOnlyApi

Declaring compile only API dependencies

Gradle currently does not automatically check if the dependency declarations are in sync.This may be added in future versions.

For more details on declaring module dependencies, please refer todocumentation on the Java Module System.

Declaring package visibility and services

The Java module system supports additional more fine granular encapsulation concepts than Gradle itself currently does.For example, you explicitly need to declare which packages are part of your API and which are only visible inside your module.Some of these capabilities might be added to Gradle itself in future versions.For now, please refer todocumentation on the Java Module System to learn how to use these features in Java Modules.

Declaring module versions

Java Modules also have a version that is encoded as part of the module identity in themodule-info.class file.This version can be inspected when a module is running.

build.gradle.kts
version = "1.2"tasks.compileJava {    // use the project's version or define one directly    options.javaModuleVersion = provider { version as String }}
build.gradle
version = '1.2'tasks.named('compileJava') {    // use the project's version or define one directly    options.javaModuleVersion = provider { version }}

Using libraries that are not modules

You probably want to use external libraries, like OSS libraries from Maven Central, in your modular Java project.Some libraries, in their newer versions, are already full modules with a module descriptor.For example,com.google.code.gson:gson:2.8.9 that has the module namecom.google.gson.

Others, likeorg.apache.commons:commons-lang3:3.10, may not offer a full module descriptor but will at least contain anAutomatic-Module-Name entry in their manifest file to define the module’s name (org.apache.commons.lang3 in the example).Such modules, that only have a name as module description, are calledautomatic module that exportall their packages and can readall modules on the module path.

A third case are traditional libraries that provide no module information at all — for examplecommons-cli:commons-cli:1.4.Gradle puts such libraries on the classpath instead of the module path.The classpath is then treated as one module (the so calledunnamed module) by Java.

build.gradle.kts
dependencies {    implementation("com.google.code.gson:gson:2.8.9")       // real module    implementation("org.apache.commons:commons-lang3:3.10") // automatic module    implementation("commons-cli:commons-cli:1.4")           // plain library}
build.gradle
dependencies {    implementation 'com.google.code.gson:gson:2.8.9'       // real module    implementation 'org.apache.commons:commons-lang3:3.10' // automatic module    implementation 'commons-cli:commons-cli:1.4'           // plain library}
Module dependencies declared in module-info.java file
module org.gradle.sample.lib {    requires com.google.gson;          // real module    requires org.apache.commons.lang3; // automatic module    // commons-cli-1.4.jar is not a module and cannot be required}

While a real module cannot directly depend on the unnamed module (only by adding command line flags), automatic modules can also see the unnamed module.Thus, if you cannot avoid to rely on a library without module information, you can wrap that library in an automatic module as part of your project.How you do that is described in the next section.

Another way to deal with non-modules is to enrich existing Jars with module descriptors yourself usingartifact transforms.This sample contains a smallbuildSrc plugin registering such a transform which you may use and adjust to your needs.This can be interesting if you want to build a fullymodular application and want the java runtime to treat everything as a real module.

Disabling Java Module support

In rare cases, you might want to disable the built-in Java Module support and define the module path by other means.To achieve this, you can disable the functionality to automatically put any Jar on the module path.Then Gradle puts Jars with module information on the classpath, even if you have amodule-info.java in your source set.This corresponds to the behaviour of Gradle versions <7.0.

To make this work, you need to setmodularity.inferModulePath = false on the Java extension (for all tasks) or on individual tasks.

build.gradle.kts
java {    modularity.inferModulePath = false}tasks.compileJava {    modularity.inferModulePath = false}
build.gradle
java {    modularity.inferModulePath = false}tasks.named('compileJava') {    modularity.inferModulePath = false}

Building an automatic module

If you can, you should always write completemodule-info.java descriptors for your modules.Still, there are a few cases where you might consider to (initally) only provide amodule name for an automatic module:

  • You are working on a library that isnot a module but you want to make it usable as such in the next release.Adding anAutomatic-Module-Name is a good first step (most popular OSS libraries on Maven central have done it by now).

  • As discussed in the previous section, an automatic module can be used as an adapter between your real modules and a traditional library on the classpath.

To turn a normal Java project into anautomatic module, just add the manifest entry with the module name:

build.gradle.kts
tasks.jar {    manifest {        attributes("Automatic-Module-Name" to "org.gradle.sample")    }}
build.gradle
tasks.named('jar') {    manifest {        attributes('Automatic-Module-Name': 'org.gradle.sample')    }}
===You can define an automatic module as part of a multi-project that otherwise defines real modules (e.g. as an adapter to another library).While this works fine in the Gradle build, such automatic module projects are not correctly recognized by IDEA/Eclipse at the moment.You can work around it by manually adding the Jar built for the automatic module to the dependencies of the project that does not find it in the IDE’s UI.===

Using classes instead of jar for compilation

A feature of thejava-library plugin is that projects which consume the library only require the classes folder for compilation, instead of the full JAR.This enables lighter inter-project dependencies as resources processing (processResources task) and archive construction (jar task) are no longer executed when only Java code compilation is performed during development.

The usage or not of the classes output instead of the JAR is aconsumer decision.For example, Groovy consumers will request classesand processed resources as these may be needed for executing AST transformation as part of the compilation process.

Increased memory usage for consumers

An indirect consequence is that up-to-date checking will require more memory, because Gradle will snapshot individual class files instead of a single jar.This may lead to increased memory consumption for large projects, with the benefit of having thecompileJava task up-to-date in more cases (e.g. changing resources no longer changes the input forcompileJava tasks of upstream projects)

Significant build performance drop on Windows for huge multi-projects

Another side effect of the snapshotting of individual class files, only affecting Windows systems, is that the performance can significantly drop when processing a very large amount of class files on the compile classpath.This only concerns very large multi-projects where a lot of classes are present on the classpath by using manyapi dependencies.To mitigate this, you can set theorg.gradle.java.compile-classpath-packaging system property totrue to change the behavior of the Java Library plugin to use jars instead of class folders for everything on the compile classpath.Note, since this has other performance impacts and potentially side effects, by triggering all jar tasks at compile time, it is only recommended to activate this if you suffer from the described performance issue on Windows.

Distributing a library

Aside frompublishing a library to a component repository, you may sometimes need to package a library and its dependencies in a distribution deliverable.TheJava Library Distribution Plugin is there to help you do just that.

You can submit issues directly on Github.
© 2025 Gradle, Inc. Gradle®, Develocity®, Build Scan®, and the Gradlephant logo are registered trademarks of Gradle, Inc.
Gradle
Privacy |Terms of Service

[8]ページ先頭

©2009-2025 Movatter.jp