Creating Dependency Configurations
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.
Configurations are added automatically by applied plugins or can be created manually within your build.
Configurations aren’t used just for declaring dependencies, they serve various roles in dependency management.
Declarable, Resolvable, and Consumable configurations
There are 3 types of configurations that fulfil different roles:
| # | Role | Name | What it means | Java example |
|---|---|---|---|---|
1 |
Bucket Role |
Declarable configuration |
Define a set of dependencies. |
|
2 |
Consumer Role |
Resolvable configuration |
Resolve dependencies into concrete artifacts/files. |
|
3 |
Producer Role |
Consumable configuration |
Expose built artifacts for other projects to consume. |
|
1. Declarable configurations
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:
dependencies {
// add a project dependency to the implementation configuration
implementation(project(":lib"))
}
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. Resolvable configurations
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.
The following shows three different APIs available to create a resolvable configuration called compileClasspath:
configurations {
// declare a resolvable configuration that is going to resolve the compile classpath of the application
// Using lazy & newer API: realized only when needed
resolvable("compileClasspath") {
extendsFrom(implementation)
}
// Using lazy & older API: realized only when needed
register("compileClasspath-lazy") {
isCanBeConsumed = false
isCanBeDeclared = false
extendsFrom(implementation)
}
// Using eager & older API: realized immediately - avoid
create("compileClasspath-eager") {
isCanBeConsumed = false
isCanBeDeclared = false
extendsFrom(implementation)
}
}
configurations {
// declare a resolvable configuration that is going to resolve the compile classpath of the application
// Using lazy & newer API: realized only when needed
resolvable("compileClasspath") {
extendsFrom(implementation)
}
// Using lazy & older API: realized only when needed
register("compileClasspath-lazy") {
canBeConsumed = false
canBeDeclared = false
extendsFrom(implementation)
}
// Using eager & older API: realized immediately - avoid
create("compileClasspath-eager") {
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. Consumable configurations
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.
The following shows three different APIs available to create a consumable configuration called exposedApi:
configurations {
// a consumable configuration meant for consumers that need the API of this component
// Using lazy & newer API: realized only when needed
consumable("exposedApi") {
extendsFrom(implementation)
}
// Using lazy & older API: realized only when needed
register("exposedApi-lazy") {
isCanBeResolved = false
isCanBeDeclared = false
extendsFrom(implementation)
}
// Using eager & older API: realized immediately - avoid
create("exposedApi-eager") {
isCanBeResolved = false
isCanBeDeclared = false
extendsFrom(implementation)
}
}
configurations {
// a consumable configuration meant for consumers that need the API of this component
// Using lazy & newer API: realized only when needed
consumable("exposedApi") {
extendsFrom(implementation)
}
// Using lazy & older API: realized only when needed
register("exposedApi-lazy") {
canBeResolved = false
canBeDeclared = false
extendsFrom(implementation)
}
// Using eager & older API: realized immediately - avoid
create("exposedApi-eager") {
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 and are required when using older APIs.
Configurations added by Plugins
Gradle plugins often create configurations (like implementation, api, testImplementation) for you.
Let’s look at a few popular ones.
Java Plugin
When you apply the Java or Java Library (java, java-library, application) plugin, Gradle adds the following configurations.
-
Declarable (you put dependencies into these):
-
api(java-libraryonly),implementation,compileOnly,runtimeOnly, plus test variants (testImplementation,testCompileOnly,testRuntimeOnly), etc.
-
-
Resolvable (you resolve these to get jars/dirs):
-
compileClasspath,runtimeClasspath, and their test equivalents (testCompileClasspath,testRuntimeClasspath).
-
-
Consumable (Gradle publishes these so other projects can depend on you):
-
apiElements(java-libraryonly) andruntimeElements.
-
plugins {
`java-library`
}
dependencies {
implementation("org.hibernate:hibernate-core:3.6.7.Final")
testImplementation("junit:junit:4.+")
api("com.google.guava:guava:23.0")
}
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'
}
There is a relationship between these configurations.
-
What you declare in
api:-
Your project: on
compileClasspathand (if needed)runtimeClasspath. -
Consumers: exported via
apiElements→ ends up on consumers’ compile and runtime classpaths.
-
-
What you declare in
implementation:-
Your project: on
compileClasspathandruntimeClasspath. -
Consumers: not exported (kept internal). It’s not on consumers’ compile classpath; the runtime part is included in your
runtimeElementsso consumers can run you without needing to compile against those internals.
-
-
What you declare in
compileOnly:-
Your project: only on
compileClasspath(not onruntimeClasspath). -
Consumers: not exported (neither
apiElementsnorruntimeElements).
-
-
What you declare in
runtimeOnly:-
Your project: only on
runtimeClasspath. -
Consumers: included in
runtimeElements(so they can run you), but not inapiElements.
-
-
Test configurations (
testImplementation, etc.) affect only test resolvable classpaths (testCompileClasspath,testRuntimeClasspath) and are never published to consumers.
Android Gradle Plugin
When you apply the Android plugin (com.android.application or com.android.library), Gradle and the Android Gradle Plugin (AGP) add a richer set of configurations to handle multiple build variants (combinations of build types and product flavors).
Each variant has its own declarable, resolvable, and consumable configurations, following the same conceptual model as the Java plugin.
-
Declarable (you put dependencies into these):
-
implementation,compileOnly,runtimeOnly,annotationProcessor, and test equivalents (androidTestImplementation,androidTestCompileOnly,androidTestRuntimeOnly,testImplementation,testCompileOnly,testRuntimeOnly), plus variant-specific versions such asdebugImplementation,releaseImplementation, and flavor-specific configurations (e.g.,freeImplementation,paidImplementation).
-
-
Resolvable (Gradle resolves these to get the actual JARs, AARs, or directories):
-
Each build variant defines its own resolvable classpaths:
debugCompileClasspath,debugRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath, and similar sets for test variants (testDebugCompileClasspath,testDebugRuntimeClasspath, etc.). These are used internally by AGP tasks such ascompileDebugJavaWithJavacormergeReleaseResources.
-
-
Consumable (Gradle publishes these so other modules can depend on your Android library):
-
For library modules (
com.android.library), AGP defines variant-specific consumable configurations:debugApiElements,debugRuntimeElements,releaseApiElements, andreleaseRuntimeElements. These control what other modules receive when they declare a dependency on a particular variant of your library.
-
plugins {
id("com.android.application") version "8.13.0"
id("org.jetbrains.kotlin.android") version "2.1.20"
id("org.jetbrains.kotlin.kapt") version "2.1.20"
}
dependencies {
implementation("androidx.core:core-ktx:1.13.1")
kapt("androidx.room:room-compiler:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
}
plugins {
id("com.android.application") version '8.13.0'
id("org.jetbrains.kotlin.android") version '2.1.20'
id("org.jetbrains.kotlin.kapt") version "2.1.20"
}
dependencies {
implementation("androidx.core:core-ktx:1.13.1")
kapt("androidx.room:room-compiler:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.2.1")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
}
There is a relationship between these configurations, similar to the Java plugin, but scoped per variant.
-
What you declare in
implementation:-
Your module: included in that variant’s
compileClasspathandruntimeClasspath. -
Consumers: not exported; remains internal to that variant.
-
-
What you declare in
api(for Android library modules):-
Your module: visible on that variant’s
compileClasspathandruntimeClasspath. -
Consumers: exported via that variant’s
apiElementsconfiguration → appears on consumers’ compile and runtime classpaths.
-
-
What you declare in
compileOnlyorannotationProcessor:-
Your module: used only for compilation; not packaged or exposed to consumers.
-
-
What you declare in
runtimeOnly:-
Your module: added only to the variant’s runtime classpath.
-
Consumers: included in the corresponding
runtimeElementsvariant, so downstream modules can run your library.
-
-
Test configurations (
testImplementation,androidTestImplementation, etc.) affect only their respective resolvable classpaths (testCompileClasspath,androidTestCompileClasspath, etc.) and are never published to consumers.
Kotlin Multiplatform Plugin
When you apply the Kotlin Multiplatform plugin (org.jetbrains.kotlin.multiplatform), Gradle creates configurations for each source set and target.
The same underlying concepts apply — declarable, resolvable, and consumable — but they’re managed per source set instead of per variant or build type.
-
Declarable (you put dependencies into these):
-
Each source set has its own declarable configuration, for example:
-
commonMainImplementation,commonMainApi,commonMainCompileOnly -
jvmMainImplementation,jsMainImplementation,iosMainImplementation, etc. -
and corresponding test configurations such as
commonTestImplementation,jvmTestImplementation, and so on. -
These are where you declare dependencies that apply to a specific source set or to all targets sharing that source set.
-
-
Resolvable (Gradle resolves these to get JARs, KLIBs, or directories):
-
Every compilable source set has a corresponding resolvable configuration, such as:
-
commonMainCompileClasspath,jvmMainCompileClasspath,iosArm64MainCompileClasspath, etc. -
These are used internally by Kotlin compilation tasks like
compileKotlinJvm,compileKotlinJs, andcompileKotlinIosArm64to obtain the appropriate libraries and dependencies for that target.
-
-
Consumable (Gradle publishes these so other modules or projects can depend on your multiplatform component):
-
KMP creates consumable configurations for each target, such as:
-
jvmApiElements,jvmRuntimeElements,jsApiElements,iosArm64ApiElements, etc. -
When you publish a KMP library, these configurations define the artifacts and metadata that Gradle and consumers use to resolve the right variant for their platform.
-
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidApplication)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
kotlin {
androidTarget {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
sourceSets {
androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
}
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.androidx.lifecycle.viewmodelCompose)
implementation(libs.androidx.lifecycle.runtimeCompose)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
}
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidApplication)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
}
kotlin {
androidTarget {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
sourceSets {
androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
}
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.androidx.lifecycle.viewmodelCompose)
implementation(libs.androidx.lifecycle.runtimeCompose)
}
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
}
There is a relationship between these configurations, similar to Java and Android, but organized per source set and target.
-
What you declare in
commonMainApi:-
Shared source sets: visible to all targets depending on
commonMain. -
Consumers: exported via the relevant
apiElementsfor each target (e.g.,jvmApiElements,iosApiElements), so downstream projects can compile against the shared API.
-
-
What you declare in
commonMainImplementation:-
Your project: included in compilation for all targets that depend on
commonMain. -
Consumers: not exported — internal only.
-
-
What you declare in target-specific configurations (e.g.,
jvmMainImplementation,iosMainImplementation):-
Your project: visible only to that target’s compilation and runtime.
-
Consumers: packaged and published through that target’s consumable configuration (
jvmRuntimeElements, etc.), but not exposed as API.
-
-
compileOnlyandruntimeOnlyequivalents work similarly per source set: used only during compilation or runtime for that target, not exported to consumers. -
Test source sets (
commonTest,jvmTest, etc.) have their own declarable and resolvable configurations, but are never published to consumers.
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:
// 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") {
extendsFrom(implementation)
}
// declare a resolvable configuration that is going to resolve the runtime classpath of the application
resolvable("runtimeClasspath") {
extendsFrom(implementation)
}
}
configurations {
// a consumable configuration meant for consumers that need the API of this component
consumable("exposedApi") {
extendsFrom(implementation)
}
// a consumable configuration meant for consumers that need the implementation of this component
consumable("exposedRuntime") {
extendsFrom(implementation)
}
}
// 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") {
extendsFrom(implementation)
}
// declare a resolvable configuration that is going to resolve the runtime classpath of the application
resolvable("runtimeClasspath") {
extendsFrom(implementation)
}
}
configurations {
// a consumable configuration meant for consumers that need the API of this component
consumable("exposedApi") {
extendsFrom(implementation)
}
// a consumable configuration meant for consumers that need the implementation of this component
consumable("exposedRuntime") {
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 fromimplementation. -
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.
Avoiding eager configuration
With lazy configuration, Gradle only initializes configurations when they’re actually needed, for example, when they’re published or consumed. To benefit from this, make sure your build logic doesn’t force configurations to realize early.
For example:
// Instead of
configurations.all {} // Eagerly realizes all configurations
// Use
configurations.configureEach {} // Only called for realized configurations
// Instead of
configurations.getByName("name").doSomething() // Eagerly realizes the "name" configuration
// Use
configurations.named("name").configure {
doSomething() // Only called if "name" configuration is realized
}
all {} iterates every configuration eagerly at configuration time.
While configureEach {} registers the action but delays running it until the configuration is actually realized.
getByName() returns and realizes the configuration immediately.
While named() returns a lazy handle so you can configure it without triggering realization.
By using these lazy APIs, Gradle can skip unnecessary work for configurations that aren’t needed, reducing configuration time and memory use.
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:
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()
}
}
}
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:
-
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
apiconfiguration for libraries that consumers will depend on, and animplementationconfiguration for libraries that are only needed internally. Theapiconfiguration is typically consumed by downstream projects, whileimplementationdependencies are hidden from consumers but used internally. -
This separation ensures that your project maintains clean boundaries between its public API and strictly internal mechanisms.
-
-
Resolvable Configuration Creation: Create a custom resolvable configuration to resolve specific sets of dependencies, like classpaths, at various build stages.
-
You might create a
compileClasspathconfiguration that resolves only the dependencies needed to compile your project. Similarly, you could create aruntimeClasspathconfiguration 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.
-
-
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
exposedApiconfiguration to expose the API dependencies of your project for consumption by other projects. Similarly, aruntimeElementsconfiguration 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. |
For example, in a project that already uses JUnit for testing, you can define a dedicated configuration named smokeTest to run smoke tests.
Each smoke test performs an HTTP request to verify a web service endpoint.
To reuse the existing test framework dependencies, the smokeTest configuration should extend from testImplementation.
This allows smoke tests to leverage the same dependencies as unit tests without duplication.
The configuration can be declared in build.gradle(.kts) as follows:
val smokeTest by configurations.creating {
extendsFrom(configurations.testImplementation.get())
}
dependencies {
testImplementation("junit:junit:4.13")
smokeTest("org.apache.httpcomponents:httpclient:4.5.5")
}
configurations {
smokeTest.extendsFrom testImplementation
}
dependencies {
testImplementation 'junit:junit:4.13'
smokeTest 'org.apache.httpcomponents:httpclient:4.5.5'
}
This setup enables the smokeTest source set to inherit JUnit and any other testing dependencies, making it easier to define and execute smoke tests while keeping them separate from unit tests.
| 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. |
val implementation = configurations.dependencyScope("implementation")
val runtimeClasspath = configurations.resolvable("runtimeClasspath") {
extendsFrom(implementation.get())
}
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.
dependencies {
implementation("com.google.guava:guava:33.2.1-jre")
}
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.