In Gradle, dependencies are associated with specific scopes, such as compile-time or runtime. These scopes are represented by configurations, each identified by a unique name.

dependency management configurations

Gradle plugins often add pre-defined configurations to your project.

For example, when applied, the Java plugin adds configurations to your project for source code compilation (implementation), test execution (testImplementation), and more (api, compileOnly, runtimeOnly, etc.):

build.gradle.kts
plugins {
    `java-library`
}
dependencies {
    implementation("org.hibernate:hibernate-core:3.6.7.Final")
    testImplementation("junit:junit:4.+")
    api("com.google.guava:guava:23.0")
}
build.gradle
plugins {
    id 'java-library'
}
dependencies {
    implementation 'org.hibernate:hibernate-core:3.6.7.Final'
    testImplementation 'junit:junit:4.+'
    api 'com.google.guava:guava:23.0'
}

This example highlights dependencies declared on the implementation, testImplementation, and api configuration for a Java project. See the Java plugin documentation for details.

Resolvable and consumable configurations

Configurations aren’t used just for declaring dependencies, they serve various roles in dependency management:

  1. Declaring Dependencies Role: Configurations that define a set of dependencies.

  2. Consumer Role: Configurations that are used to resolve dependencies into artifacts.

  3. Producer Role: Configurations that expose artifacts for consumption by other projects.

1. Configurations for declaring dependencies (i.e, declarable configuration)

To declare dependencies in your project, you can use or create declarable configurations. These configurations help organize and categorize dependencies for different parts of the project.

For example, to express a dependency on another project, you would use a declarable configurations like implementation:

build.gradle.kts
dependencies {
    // add a project dependency to the implementation configuration
    implementation(project(":lib"))
}
build.gradle
dependencies {
    // add a project dependency to the implementation configuration
    implementation project(":lib")
}

Configurations used for declaring dependencies define and manage the specific libraries or projects your code requires for tasks such as compilation or testing.

2. Configurations for consumers (i.e, resolvable configuration)

To control how dependencies are resolved and used within your project, you can use or create resolvable configurations. These configurations define classpaths and other sets of artifacts that your project needs during different stages, like compilation or runtime.

For example, the implementation configuration declares the dependencies, while compileClasspath and runtimeClasspath are resolvable configurations designed for specific purposes. When resolved, they represent the classpaths needed for compilation and runtime, respectively:

build.gradle.kts
configurations {
    // declare a resolvable configuration that is going to resolve the compile classpath of the application
    resolvable("compileClasspath") {
        //isCanBeConsumed = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
}
build.gradle
configurations {
    // declare a resolvable configuration that is going to resolve the compile classpath of the application
    resolvable("compileClasspath") {
        //canBeConsumed = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
}

Resolvable configurations are those that can be resolved to produce a set of files or artifacts. These configurations are used to define the classpath for different stages of a build process, such as compilation or runtime.

3. Configurations for producers (i.e., consumable configuration)

Consumable configurations are used to expose artifacts to other projects. These configurations define what parts of your project can be consumed by others, like APIs or runtime dependencies, but are not meant to be resolved directly within your project.

For example, the exposedApi configuration is a consumable configuration that exposes the API of a component to consumers:

build.gradle.kts
configurations {
    // a consumable configuration meant for consumers that need the API of this component
    consumable("exposedApi") {
        //isCanBeResolved = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
}
build.gradle
configurations {
    // a consumable configuration meant for consumers that need the API of this component
    consumable("exposedApi") {
        //canBeResolved = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
}

A library typically provides consumable configurations like apiElements (for compilation) and runtimeElements (for runtime dependencies). These configurations expose the necessary artifacts for other projects to consume, without being resolvable within the current project. The canBeDeclared, isCanBeConsumed and isCanBeResolved flags help distinguish the roles of these configurations.

Configuration flags and roles

Configurations have three key flags:

  • canBeResolved: Indicates that this configuration is intended for resolving a set of dependencies into a dependency graph. A resolvable configuration should not be declarable or consumable.

  • canBeConsumed: Indicates that this configuration is intended for exposing artifacts outside this project. A consumable configuration should not be declarable or resolvable.

  • canBeDeclared: Indicates that this configuration is intended for declaring dependencies. A declarable configuration should not be resolvable or consumable.

Configurations should only have one of these flags enabled.

In short, a configuration’s role is determined by the canBeResolved, canBeConsumed, or canBeDeclared flag:

Configuration role Can be resolved Can be consumed Can be declared

Dependency Scope

false

false

true

Resolve for certain usage

true

false

false

Exposed to consumers

false

true

false

Legacy, don’t use

true

true

true

For backwards compatibility, the flags have a default value of true, but as a plugin author, you should always determine the right values for those flags, or you might accidentally introduce resolution errors.

This example demonstrates how to manually declare the core Java configurations (normally provided by the Java plugin) in Gradle:

build.gradle.kts
// declare a "configuration" named "implementation"
val implementation by configurations.creating {
    isCanBeConsumed = false
    isCanBeResolved = false
}

dependencies {
    // add a project dependency to the implementation configuration
    implementation(project(":lib"))
}

configurations {
    // declare a resolvable configuration that is going to resolve the compile classpath of the application
    resolvable("compileClasspath") {
        //isCanBeConsumed = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
    // declare a resolvable configuration that is going to resolve the runtime classpath of the application
    resolvable("runtimeClasspath") {
        //isCanBeConsumed = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
}

configurations {
    // a consumable configuration meant for consumers that need the API of this component
    consumable("exposedApi") {
        //isCanBeResolved = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
    // a consumable configuration meant for consumers that need the implementation of this component
    consumable("exposedRuntime") {
        //isCanBeResolved = false
        //isCanBeDeclared = false
        extendsFrom(implementation)
    }
}
build.gradle
// declare a "configuration" named "implementation"
configurations {
    // declare a "configuration" named "implementation"
    implementation {
        canBeConsumed = false
        canBeResolved = false
    }
}

dependencies {
    // add a project dependency to the implementation configuration
    implementation project(":lib")
}

configurations {
    // declare a resolvable configuration that is going to resolve the compile classpath of the application
    resolvable("compileClasspath") {
        //canBeConsumed = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
    // declare a resolvable configuration that is going to resolve the runtime classpath of the application
    resolvable("runtimeClasspath") {
        //canBeConsumed = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
}

configurations {
    // a consumable configuration meant for consumers that need the API of this component
    consumable("exposedApi") {
        //canBeResolved = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
    // a consumable configuration meant for consumers that need the implementation of this component
    consumable("exposedRuntime") {
        //canBeResolved = false
        //canBeDeclared = false
        extendsFrom(implementation)
    }
}

The following configurations are created:

  • implementation: Used for declaring project dependencies but neither consumed nor resolved.

  • compileClasspath + runtimeClasspath: Resolvable configurations that collect compile-time and runtime dependencies from implementation.

  • exposedApi + exposedRuntime: Consumable configurations that expose artifacts (API and runtime) to other projects, but aren’t meant for internal resolution.

This setup mimics the behavior of the implementation, compileClasspath, runtimeClasspath, apiElements, and runtimeElements configurations in the Java plugin.

Deprecated configurations

In the past, some configurations did not define which role they were intended to be used for.

A deprecation warning is emitted when a configuration is used in a way that was not intended. To fix the deprecation, you will need to stop using the configuration in the deprecated role. The exact changes required depend on how the configuration is used and if there are alternative configurations that should be used instead.

Creating custom configurations

You can define custom configurations to declare separate scopes of dependencies for specific purposes.

Suppose you want to generate Javadocs with AsciiDoc formatting embedded within your Java source code comments. By setting up the asciidoclet configuration, you enable Gradle to use Asciidoclet, allowing your Javadoc task to produce HTML documentation with enhanced formatting options:

build.gradle.kts
val asciidoclet by configurations.creating

dependencies {
    asciidoclet("org.asciidoctor:asciidoclet:1.+")
}

tasks.register("configureJavadoc") {
    doLast {
        tasks.javadoc {
            options.doclet = "org.asciidoctor.Asciidoclet"
            options.docletpath = asciidoclet.files.toList()
        }
    }
}
build.gradle
configurations {
    asciidoclet
}

dependencies {
    asciidoclet 'org.asciidoctor:asciidoclet:1.+'
}

You can manage custom configurations using the configurations block. Configurations must have names and can extend each other. For more details, refer to the ConfigurationContainer API.

Configurations are intended to be used for a single role: declaring dependencies, performing resolution, or defining consumable variants.

There are three main use cases for creating custom configurations:

  1. API/Implementation Separation: Create custom configurations to separate API dependencies (exposed to consumers) from implementation dependencies (used internally during compilation or runtime).

    • You might create an api configuration for libraries that consumers will depend on, and an implementation configuration for libraries that are only needed internally. The api configuration is typically consumed by downstream projects, while implementation dependencies are hidden from consumers but used internally.

    • This separation ensures that your project maintains clean boundaries between its public API and strictly internal mechanisms.

  2. Resolvable Configuration Creation: Create a custom resolvable configuration to resolve specific sets of dependencies, like classpaths, at various build stages.

    • You might create a compileClasspath configuration that resolves only the dependencies needed to compile your project. Similarly, you could create a runtimeClasspath configuration to resolve the dependencies needed to run the project at runtime.

    • This allows fine-grained control over which dependencies are available during different build phases, such as compilation or testing.

  3. Consumable Configuration from Dependency Configuration: Create a custom consumable configuration to expose artifacts or dependencies for other projects to consume, typically when your project produces artifacts like JARs.

    • You might create an exposedApi configuration to expose the API dependencies of your project for consumption by other projects. Similarly, a runtimeElements configuration could be created to expose the runtime dependencies or artifacts that other projects need.

    • Consumable configurations ensure that only the necessary artifacts or dependencies are exposed to consumers.

Configuration API incubating methods

Several incubating factory methods—resolvable(), consumable(), and dependencyScope()—within the ConfigurationContainer API can be used to simplify the creation of configurations with specific roles.

These methods help build authors document the purpose of a configuration and avoid manually setting various configuration flags, streamlining the process and ensuring consistency:

  • resolvable(): Creates a configuration intended for resolving dependencies. This means the configuration can be used to resolve dependencies but not consumed by other projects.

  • consumable(): Creates a configuration meant to be consumed by other projects but not used to resolve dependencies itself.

  • dependencyScope(): Creates a configuration that establishes a dependency scope, setting up the necessary properties to act both as a consumer and provider, depending on the use case.

Configuration inheritance

Configurations can inherit from other configurations, creating an inheritance hierarchy.

Configurations form an inheritance hierarchy using the Configuration.extendsFrom(Configuration…​) method. A configuration can extend any other configuration other than a detached configuration, regardless of how it is defined in the build script or plugin.

Avoid extending consumable or resolvable configurations with configurations that are not consumable or resolvable, respectively.

Configurations can only extend configurations within the same project.

When extending a configuration, the new configuration inherits:

  • dependencies

  • dependency constraints

  • exclude rules

  • artifacts

  • capabilities

The extension does not include attributes. It also does not extend consumable/resolvable/declarable status.

Dependency resolution

The entrypoint to all dependency resolution APIs is a resolvable Configuration. The Java plugins primarily use the compileClasspath, and runtimeClasspath configurations to resolve jars for compilation and runtime respectively.

A resolvable configuration is intended for initiating dependency resolution. The dependencies to be resolved are declared on dependency scope configurations. The Java plugins use the api, implementation, and runtimeOnly dependency scope configurations, among others, as a source of dependencies to be resolved by the resolvable configurations.

Consider the following example that demonstrates how to declare a set of configurations intended for resolution:

This example uses incubating APIs.
build.gradle.kts
val implementation = configurations.dependencyScope("implementation")
val runtimeClasspath = configurations.resolvable("runtimeClasspath") {
    extendsFrom(implementation.get())
}
build.gradle
configurations {
    dependencyScope("implementation")
    resolvable("runtimeClasspath") {
        extendsFrom(implementation)
    }
}

Dependencies can be declared on the implementation configuration using the dependencies block. See the Declaring Dependencies chapter for more information on the types of dependencies that can be declared, and the various options for customizing dependency declarations.

build.gradle.kts
dependencies {
    implementation("com.google.guava:guava:33.2.1-jre")
}
build.gradle
dependencies {
    implementation("com.google.guava:guava:33.2.1-jre")
}

Now that we’ve created a dependency scope configuration for declaring dependencies, and a resolvable configuration for resolving those dependencies, we can use Gradle’s dependency resolution APIs to access the results of resolution.

Unsafe configuration resolution errors

Resolving a configuration can have side effects on Gradle’s project model. As a result, Gradle must manage access to each project’s configurations.

There are a number of ways a configuration might be resolved unsafely. For example:

  • A task from one project directly resolves a configuration in another project in the task’s action.

  • A task specifies a configuration from another project as an input file collection.

  • A build script for one project resolves a configuration in another project during evaluation.

  • Project configurations are resolved in the settings file.

Gradle produces a deprecation warning for each unsafe access.

Unsafe access can cause indeterminate errors. You should fix unsafe access warnings in your build.

In most cases, you can resolve unsafe accesses by creating a proper cross-project dependency.