# Using Resolution Rules

version 9.6.0-20260602042426+0000

Gradle provides several mechanisms to directly influence the behavior of the dependency resolution engine.

Unlike dependency constraints or component metadata rules, which serve as inputs to the resolution process, these mechanisms allow you to inject rules directly into the resolution engine. Because of their direct impact, they can be considered brute-force solutions that may mask underlying issues, such as the introduction of new dependencies.

| It’s generally advisable to resort to resolution rules only when other approaches are insufficient.

If you’re developing a library, it’s best to use dependency constraints, as they are shared with your consumers.

Here are the key resolution strategies in Gradle:

# | Strategy | Info
--- | --- | ---
1 | Forcing Dependency Versions | Force a specific version of a dependency.
2 | Module Replacement | Substitute one module for another with an explanation.
3 | Dependency Substitution | Substitute dependencies dynamically.
4 | Component Selection Rules | Control which versions of a module are allowed. Reject specific versions that are known to be broken or undesirable.
5 | Setting Default Dependencies | Automatically add dependencies to a configuration when no dependencies are explicitly declared.
6 | Excluding Transitive Dependencies | Exclude transitive dependencies that you don’t want to be included in the dependency graph.
7 | Force Failed Resolution Strategies | Force builds to fail when certain conditions occur during resolution.
8 | Disabling Transitive Dependencies | Dependencies are transitive by default, but you can disable this behavior for individual dependencies.
9 | Dependency Resolve Rules and Other Conditionals | Transform or filter dependencies directly as they are resolved and other corner case scenarios.

## 1. Forcing Dependency Versions

You can enforce a specific version of a dependency, regardless of what versions might be requested or resolved by other parts of the build script.

This is useful for ensuring consistency and avoiding conflicts due to different versions of the same dependency being used.

build.gradle.kts

    configurations {
        "compileClasspath" {
            resolutionStrategy.force("commons-codec:commons-codec:1.9")
        }
    }

    dependencies {
        implementation("org.apache.httpcomponents:httpclient:4.5.4")
    }

build.gradle

    configurations {
        compileClasspath {
            resolutionStrategy.force 'commons-codec:commons-codec:1.9'
        }
    }

    dependencies {
        implementation 'org.apache.httpcomponents:httpclient:4.5.4'
    }

| Forcing a specific version of a dependency should be a conscious and deliberate decision. It can lead to conflicts or unexpected behavior, especially when transitive dependencies rely on different versions.

## 2. Module Replacement

While it’s generally better to manage module conflicts using capabilities, there are scenarios—especially when working with older versions of Gradle-that require a different approach. In these cases, module replacement rules offer a solution by allowing you to specify that a legacy library has been replaced by a newer one.

Module replacement rules allow you to declare that a legacy library has been replaced by a newer one. For instance, the migration from `google-collections` to `guava` involved renaming the module from `com.google.collections:google-collections` to `com.google.guava:guava`. Such changes impact conflict resolution because Gradle doesn’t treat them as version conflicts due to different module coordinates.

Consider a scenario where both libraries appear in the dependency graph. Your project depends on `guava`, but a transitive dependency pulls in `google-collections`. This can cause runtime errors since Gradle won’t automatically resolve this as a conflict. Common solutions include:

- Declaring an exclusion rule to avoid `google-collections`.

- Avoiding dependencies that pull in legacy libraries.

- Upgrading dependencies that no longer use `google-collections`.

- Downgrading to `google-collections` (not recommended).

- Assigning capabilities so `google-collections` and `guava` are mutually exclusive.

These methods can be insufficient for large-scale projects. By declaring module replacements, you can address this issue globally across projects, allowing organizations to handle such conflicts holistically.

build.gradle.kts

    dependencies {
        modules {
            module("com.google.collections:google-collections") {
                replacedBy("com.google.guava:guava", "google-collections is now part of Guava")
            }
        }
    }

build.gradle

    dependencies {
        modules {
            module("com.google.collections:google-collections") {
                replacedBy("com.google.guava:guava", "google-collections is now part of Guava")
            }
        }
    }

Once declared, Gradle treats any version of `guava` as superior to `google-collections` during conflict resolution, ensuring only `guava` appears in the classpath. However, if `google-collections` is the only module present, it won’t be automatically replaced unless there’s a conflict.

For more examples, refer to the DSL reference for ComponentMetadataHandler.

| Gradle does not currently support replacing a module with multiple modules, but multiple modules can be replaced by a single module.

## 3. Dependency Substitution

Dependency substitution rules allow for replacing project and module dependencies with specified alternatives, making them interchangeable. While similar to dependency resolve rules, they offer more flexibility by enabling substitution between project and module dependencies.

However, adding a dependency substitution rule affects the timing of configuration resolution. Instead of resolving on first use, the configuration is resolved during task graph construction, which can cause issues if the configuration is modified later or depends on modules published during task execution.

Explanation:

- A configuration can serve as input to a task and include project dependencies when resolved.

- If a project dependency is an input to a task (via a configuration), then tasks to build those artifacts are added as dependencies.

- To determine project dependencies that are inputs to a task, Gradle must resolve the configuration inputs.

- Because the Gradle task graph is fixed once task execution has commenced, Gradle needs to perform this resolution prior to executing any tasks.

Without substitution rules, Gradle assumes that external module dependencies don’t reference project dependencies, simplifying dependency traversal. With substitution rules, this assumption no longer holds, so Gradle must fully resolve the configuration to determine project dependencies.

### Substituting an external module dependency with a project dependency

Dependency substitution can be used to replace an external module with a locally developed project, which is helpful when testing a patched or unreleased version of a module.

The external module can be replaced whether a version is specified:

build.gradle.kts

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            substitute(module("org.utils:api"))
                .using(project(":api")).because("we work with the unreleased development version")
            substitute(module("org.utils:util:2.5")).using(project(":util"))
        }
    }

build.gradle

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            substitute module("org.utils:api") using project(":api") because "we work with the unreleased development version"
            substitute module("org.utils:util:2.5") using project(":util")
        }
    }

- Substituted projects must be part of the multi-project build (included via `settings.gradle`).

- The substitution replaces the module dependency with the project dependency and sets up task dependencies, but doesn’t automatically include the project in the build.

### Substituting a project dependency with a module replacement

You can also use substitution rules to replace a project dependency with an external module in a multi-project build.

This technique can accelerate development by allowing certain dependencies to be downloaded from a repository instead of being built locally:

build.gradle.kts

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            substitute(project(":api"))
                .using(module("org.utils:api:1.3")).because("we use a stable version of org.utils:api")
        }
    }

build.gradle

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            substitute project(":api") using module("org.utils:api:1.3") because "we use a stable version of org.utils:api"
        }
    }

- The substituted module must include a version.

- Even after substitution, the project remains part of the multi-project build, but tasks to build it won’t be executed when resolving the configuration.

### Conditionally substituting a dependency

You can conditionally substitute a module dependency with a local project in a multi-project build using dependency substitution rules.

This is particularly useful when you want to use a locally developed version of a dependency if it exists, otherwise fall back to the external module:

build.gradle.kts

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution.all {
            requested.let {
                if (it is ModuleComponentSelector && it.group == "org.example") {
                    val targetProject = findProject(":${it.module}")
                    if (targetProject != null) {
                        useTarget(targetProject)
                    }
                }
            }
        }
    }

build.gradle

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution.all { DependencySubstitution dependency ->
            if (dependency.requested instanceof ModuleComponentSelector && dependency.requested.group == "org.example") {
                def targetProject = findProject(":${dependency.requested.module}")
                if (targetProject != null) {
                    dependency.useTarget targetProject
                }
            }
        }
    }

- The substitution only occurs if a local project matching the dependency name is found.

- The local project must already be included in the multi-project build (via `settings.gradle`).

### Substituting a dependency with another variant

You can substitute a dependency with another variant, such as switching between a platform dependency and a regular library dependency.

This is useful when your build process needs to change the type of dependency based on specific conditions:

- The substitution is based on the requested dependency’s attributes (like group and version).

- This approach allows you to switch from a platform component to a library or vice versa.

- It uses Gradle’s variant-aware engine to ensure the correct variant is selected based on the configuration and consumer attributes.

This flexibility is often required when working with complex dependency graphs where different component types (platforms, libraries) need to be swapped dynamically.

#### Substituting a dependency with attributes

Substituting a dependency based on attributes allows you to override the default selection of a component by targeting specific attributes (like platform vs. regular library).

This technique is useful for managing platform and library dependencies in complex builds, particularly when you want to consume a regular library but the platform dependency was incorrectly declared:

lib/build.gradle.kts

    dependencies {
        // This is a platform dependency but you want the library
        implementation(platform("com.google.guava:guava:28.2-jre"))
    }

lib/build.gradle

    dependencies {
        // This is a platform dependency but you want the library
        implementation platform('com.google.guava:guava:28.2-jre')
    }

In this example, the substitution rule targets the platform version of `com.google.guava:guava` and replaces it with the regular library version:

consumer/build.gradle.kts

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            substitute(platform(module("com.google.guava:guava:28.2-jre")))
                .using(module("com.google.guava:guava:28.2-jre"))
        }
    }

consumer/build.gradle

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            substitute(platform(module('com.google.guava:guava:28.2-jre'))).
                using module('com.google.guava:guava:28.2-jre')
        }
    }

Without the `platform` keyword, the substitution would not specifically target the platform dependency.

The following rule performs the same substitution but uses the more granular variant notation, allowing for customization of the dependency’s attributes:

consumer/build.gradle.kts

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            substitute(variant(module("com.google.guava:guava:28.2-jre")) {
                attributes {
                    attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.REGULAR_PLATFORM))
                }
            }).using(module("com.google.guava:guava:28.2-jre"))
        }
    }

consumer/build.gradle

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            substitute variant(module('com.google.guava:guava:28.2-jre')) {
                attributes {
                    attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.REGULAR_PLATFORM))
                }
            } using module('com.google.guava:guava:28.2-jre')
        }
    }

By using attribute-based substitution, you can precisely control which dependencies are replaced, ensuring Gradle resolves the correct versions and variants in your build.

Refer to the DependencySubstitutions API for a complete reference.

| In composite builds, the rule that you have to match the exact requested dependency attributes is not applied. When using composites, Gradle will automatically match the requested attributes. In other words, it is implicit that if you include another build, you are substituting all variants of the substituted module with an equivalent variant in the included build.

#### Substituting a dependency with a dependency with capabilities

You can substitute a dependency with a different variant that includes specific capabilities. Capabilities allow you to specify that a particular variant of a dependency offers a set of related features or functionality, such as test fixtures.

This example substitutes a regular dependency with its test fixtures using a capability:

build.gradle.kts

    configurations.testCompileClasspath {
        resolutionStrategy.dependencySubstitution {
            substitute(module("com.acme:lib:1.0")).using(variant(module("com.acme:lib:1.0")) {
                capabilities {
                    requireCapability("com.acme:lib-test-fixtures")
                }
            })
        }
    }

build.gradle

    configurations.testCompileClasspath {
        resolutionStrategy.dependencySubstitution {
            substitute(module('com.acme:lib:1.0'))
                .using variant(module('com.acme:lib:1.0')) {
                capabilities {
                    requireCapability('com.acme:lib-test-fixtures')
                }
            }
        }
    }

Here, we substitute the regular `com.acme:lib:1.0` dependency with its `lib-test-fixtures` variant. The `requireCapability` function specifies that the new variant must have the `com.acme:lib-test-fixtures` capability, ensuring the right version of the dependency is selected for testing purposes.

Capabilities within the substitution rule are used to precisely match dependencies, and Gradle only substitutes dependencies that match the required capabilities.

Refer to the DependencySubstitutions API for a complete reference of the variant substitution API.

### Substituting a dependency with a classifier or artifact

You can substitute dependencies that have a classifier with ones that don’t or vice versa. Classifiers are often used to represent different versions of the same artifact, such as platform-specific builds or dependencies with different APIs. Although Gradle discourages the use of classifiers, it provides a way to handle substitutions for cases where classifiers are still in use.

Consider the following setup:

consumer/build.gradle.kts

    dependencies {
        implementation("com.google.guava:guava:28.2-jre")
        implementation("co.paralleluniverse:quasar-core:0.8.0")
        implementation(project(":lib"))
    }

consumer/build.gradle

    dependencies {
        implementation 'com.google.guava:guava:28.2-jre'
        implementation 'co.paralleluniverse:quasar-core:0.8.0'
        implementation project(':lib')
    }

In the example above, the first level dependency on `quasar` makes us think that Gradle would resolve `quasar-core-0.8.0.jar` but it’s not the case.

The build fails with this message:

    Execution failed for task ':consumer:resolve'.
    > Could not resolve all files for configuration ':consumer:runtimeClasspath'.
       > Could not find quasar-core-0.8.0-jdk8.jar (co.paralleluniverse:quasar-core:0.8.0).
         Searched in the following locations:
             https://repo.maven.apache.org/maven2/co/paralleluniverse/quasar-core/0.8.0/quasar-core-0.8.0-jdk8.jar

That’s because there’s a dependency on another project, `lib`, which itself depends on a different version of `quasar-core`:

lib/build.gradle.kts

    dependencies {
        implementation("co.paralleluniverse:quasar-core:0.7.10:jdk8")
    }

lib/build.gradle

    dependencies {
        implementation "co.paralleluniverse:quasar-core:0.7.10:jdk8"
    }

- The consumer depends on `quasar-core:0.8.0` without a classifier.

- The lib project depends on `quasar-core:0.7.10` with the `jdk8` classifier.

- Gradle’s conflict resolution selects the higher version (`0.8.0`), but `quasar-core:0.8.0` doesn’t have the `jdk8` classifier, leading to a resolution error.

To resolve this conflict, you can instruct Gradle to ignore classifiers when resolving `quasar-core` dependencies:

consumer/build.gradle.kts

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            substitute(module("co.paralleluniverse:quasar-core"))
                .using(module("co.paralleluniverse:quasar-core:0.8.0"))
                .withoutClassifier()
        }
    }

consumer/build.gradle

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            substitute module('co.paralleluniverse:quasar-core') using module('co.paralleluniverse:quasar-core:0.8.0') withoutClassifier()
        }
    }

This rule effectively replaces any dependency on `quasar-core` found in the graph with a dependency without classifier.

If you need to substitute with a specific classifier or artifact, you can specify the classifier or artifact details in the substitution rule.

For more detailed information, refer to:

- Artifact selection via the Substitution DSL

- Artifact selection via the DependencySubstitution API

- Artifact selection via the ResolutionStrategy API

## 4. Component Selection Rules

Component selection rules may influence which component instance should be selected when multiple versions are available that match a version selector. Rules are applied against every available version and allow the version to be explicitly rejected.

This allows Gradle to ignore any component instance that does not satisfy conditions set by the rule. Examples include:

- For a dynamic version like `1.+` certain versions may be explicitly rejected from selection.

- For a static version like `1.4` an instance may be rejected based on extra component metadata such as the Ivy branch attribute, allowing an instance from a subsequent repository to be used.

Rules are configured via the ComponentSelectionRules object. Each rule configured will be called with a ComponentSelection object as an argument that contains information about the candidate version being considered. Calling ComponentSelection.reject(java.lang.String) causes the given candidate version to be explicitly rejected, in which case the candidate will not be considered for the selector.

The following example shows a rule that disallows a particular version of a module but allows the dynamic version to choose the next best candidate:

build.gradle.kts

    configurations {
        implementation {
            resolutionStrategy {
                componentSelection {
                    // Accept the highest version matching the requested version that isn't '1.5'
                    all {
                        if (candidate.group == "org.sample" && candidate.module == "api" && candidate.version == "1.5") {
                            reject("version 1.5 is broken for 'org.sample:api'")
                        }
                    }
                }
            }
        }
    }

    dependencies {
        implementation("org.sample:api:1.+")
    }

build.gradle

    configurations {
        implementation {
            resolutionStrategy {
                componentSelection {
                    // Accept the highest version matching the requested version that isn't '1.5'
                    all { ComponentSelection selection ->
                        if (selection.candidate.group == 'org.sample' && selection.candidate.module == 'api' && selection.candidate.version == '1.5') {
                            selection.reject("version 1.5 is broken for 'org.sample:api'")
                        }
                    }
                }
            }
        }
    }

    dependencies {
        implementation 'org.sample:api:1.+'
    }

Note that version selection is applied starting with the highest version first. The version selected will be the first version found that all component selection rules accept.

| A version is considered accepted if no rule explicitly rejects it.

Similarly, rules can be targeted at specific modules. Modules must be specified in the form of `group:module`:

build.gradle.kts

    configurations {
        create("targetConfig") {
            resolutionStrategy {
                componentSelection {
                    withModule("org.sample:api") {
                        if (candidate.version == "1.5") {
                            reject("version 1.5 is broken for 'org.sample:api'")
                        }
                    }
                }
            }
        }
    }

build.gradle

    configurations {
        create("targetConfig") {
            resolutionStrategy {
                componentSelection {
                    withModule("org.sample:api") { ComponentSelection selection ->
                        if (selection.candidate.version == "1.5") {
                            selection.reject("version 1.5 is broken for 'org.sample:api'")
                        }
                    }
                }
            }
        }
    }

Component selection rules can also consider component metadata when selecting a version. Possible additional metadata that can be considered are ComponentMetadata and IvyModuleDescriptor.

Note that this extra information may not always be available and thus should be checked for `null` values:

build.gradle.kts

    configurations {
        create("metadataRulesConfig") {
            resolutionStrategy {
                componentSelection {
                    // Reject any versions with a status of 'experimental'
                    all {
                        if (candidate.group == "org.sample" && metadata?.status == "experimental") {
                            reject("don't use experimental candidates from 'org.sample'")
                        }
                    }
                    // Accept the highest version with either a "release" branch or a status of 'milestone'
                    withModule("org.sample:api") {
                        if (getDescriptor(IvyModuleDescriptor::class)?.branch != "release" && metadata?.status != "milestone") {
                            reject("'org.sample:api' must have testing branch or milestone status")
                        }
                    }
                }
            }
        }
    }

build.gradle

    configurations {
        metadataRulesConfig {
            resolutionStrategy {
                componentSelection {
                    // Reject any versions with a status of 'experimental'
                    all { ComponentSelection selection ->
                        if (selection.candidate.group == 'org.sample' && selection.metadata?.status == 'experimental') {
                            selection.reject("don't use experimental candidates from 'org.sample'")
                        }
                    }
                    // Accept the highest version with either a "release" branch or a status of 'milestone'
                    withModule('org.sample:api') { ComponentSelection selection ->
                        if (selection.getDescriptor(IvyModuleDescriptor)?.branch != "release" && selection.metadata?.status != 'milestone') {
                            selection.reject("'org.sample:api' must be a release branch or have milestone status")
                        }
                    }
                }
            }
        }
    }

A ComponentSelection argument is always required as a parameter when declaring a component selection rule.

## 5. Default Dependencies

You can set default dependencies for a configuration to ensure that a default version is used when no explicit dependencies are specified.

This is useful for plugins that rely on versioned tools and want to provide a default if the user doesn’t specify a version:

build.gradle.kts

    configurations {
        create("pluginTool") {
            defaultDependencies {
                add(project.dependencies.create("org.gradle:my-util:1.0"))
            }
        }
    }

build.gradle

    configurations {
        pluginTool {
            defaultDependencies { dependencies ->
                dependencies.add(project.dependencies.create("org.gradle:my-util:1.0"))
            }
        }
    }

In this example, the `pluginTool` configuration will use `org.gradle:my-util:1.0` as a default dependency unless another version is specified.

## 6. Excluding Transitive Dependencies

To completely exclude a transitive dependency for a particular configuration, use the `Configuration.exclude(Map)` method.

| Excluding a transitive dependency should be a conscious and deliberate choice. Removing a dependency that other libraries rely on can lead to runtime errors or unexpected behavior. If you choose to exclude something, make sure your code doesn’t rely on it—ideally backed by comprehensive test coverage to catch potential issues early.

This approach will automatically exclude the specified transitive dependency from all dependencies declared within the configuration:

build.gradle.kts

    configurations {
        "implementation" {
            exclude(group = "commons-collections", module = "commons-collections")
        }
    }

    dependencies {
        implementation("commons-beanutils:commons-beanutils:1.9.4")
        implementation("com.opencsv:opencsv:4.6")
    }

build.gradle

    configurations {
        implementation {
            exclude group: 'commons-collections', module: 'commons-collections'
        }
    }

    dependencies {
        implementation 'commons-beanutils:commons-beanutils:1.9.4'
        implementation 'com.opencsv:opencsv:4.6'
    }

In this example, the `commons-collections` dependency will be excluded from the `implementation` configuration, regardless of whether it is a direct or transitive dependency.

Below is a simplified usage of the `commons-beanutils` library, where we only call `PropertyUtils.setSimpleProperty()` on a POJO (Plain Old Java Object):

src/main/java/Main.java

    import org.apache.commons.beanutils.PropertyUtils;

    public class Main {
        public static void main(String[] args) throws Exception {
            Object person = new Person();
            PropertyUtils.setSimpleProperty(person, "name", "Bart Simpson");
            PropertyUtils.setSimpleProperty(person, "age", 38);
        }
    }

This specific usage doesn’t require `commons-collections`, which we’ve verified with test coverage. By excluding it, we effectively express that we’re only using a subset of `commons-beanutils`. However, this can be risky. If our code were more dynamic or invoked error-handling code paths, we might encounter runtime issues.

For example, if we tried to set a property that doesn’t exist, the library may throw a `NoClassDefFoundError` for a missing class from `commons-collections` rather than a clear exception, because that path depends on the excluded library.

Gradle handles exclusions differently than Maven—it evaluates the entire dependency graph. An exclusion is only applied if all paths to a dependency agree on it.

For example, if another dependency (like `opencsv`) also brings in `commons-beanutils` (but without an exclusion), the transitive `commons-collections` will still appear:

build.gradle.kts

    dependencies {
        implementation("commons-beanutils:commons-beanutils:1.9.4") {
            exclude(group = "commons-collections", module = "commons-collections")
        }
        implementation("com.opencsv:opencsv:4.6") // depends on 'commons-beanutils' without exclude and brings back 'commons-collections'
    }

build.gradle

    dependencies {
        implementation('commons-beanutils:commons-beanutils:1.9.4') {
            exclude group: 'commons-collections', module: 'commons-collections'
        }
        implementation 'com.opencsv:opencsv:4.6' // depends on 'commons-beanutils' without exclude and brings back 'commons-collections'
    }

To fully exclude `commons-collections`, you must also exclude it from `opencsv`:

build.gradle.kts

    dependencies {
        implementation("commons-beanutils:commons-beanutils:1.9.4") {
            exclude(group = "commons-collections", module = "commons-collections")
        }
        implementation("com.opencsv:opencsv:4.6") {
            exclude(group = "commons-collections", module = "commons-collections")
        }
    }

build.gradle

    dependencies {
        implementation('commons-beanutils:commons-beanutils:1.9.4') {
            exclude group: 'commons-collections', module: 'commons-collections'
        }
        implementation('com.opencsv:opencsv:4.6') {
            exclude group: 'commons-collections', module: 'commons-collections'
        }
    }

Using excludes should be a conscious and well-tested decision. If the excluded dependency is needed in any code path, your build may fail at runtime.

Historically, excludes have also been used as a workaround for limitations in other build systems, but there are better alternatives:

- Use dependency constraints to update or override versions instead of excluding undesired ones.

- Use component metadata rules to remove an unnecessary dependency—like a compile-time dependency that’s erroneously added to the metadata of the library. This tells Gradle to ignore the dependency entirely (i.e., the metadata is wrong). Note that these rules are not published, so if you’re creating a library others will use, an explicit `exclude` may be more appropriate.

- Use capabilities to resolve mutually exclusive dependency conflicts. Some libraries can’t coexist because they provide the same capability—such as `log4j` and `log4j-over-slf4j`, or `com.google.collections` and `guava`. If Gradle isn’t aware of the conflict, declare the overlapping capability using a component metadata rule. If you’re developing a library, avoid enforcing a specific choice—let the consumers decide which implementation to use.

## 7. Force Failed Resolution Strategies

Version conflicts can be forced to fail using:

- `failOnNonReproducibleResolution()`

- `failOnDynamicVersions()`

- `failOnChangingVersions()`

- `failOnVersionConflict()`

This will fail the build when conflicting versions of the same dependency are found:

build.gradle.kts

    configurations.configureEach {
        resolutionStrategy {
            failOnVersionConflict()
        }
    }

build.gradle

    configurations.configureEach {
        resolutionStrategy {
            failOnVersionConflict()
        }
    }

## 8. Disabling Transitive Dependencies

By default, Gradle resolves all transitive dependencies for a given module.

However, there are situations where you may want to disable this behavior, such as when you need more control over dependencies or when the dependency metadata is incorrect.

You can tell Gradle to disable transitive dependency management for a dependency by setting ModuleDependency.setTransitive(boolean) to `false`.

In the following example, transitive dependency resolution is disabled for the `guava` dependency:

build.gradle.kts

    dependencies {
        implementation("com.google.guava:guava:23.0") {
            isTransitive = false
        }
    }

build.gradle

    dependencies {
        implementation('com.google.guava:guava:23.0') {
            transitive = false
        }
    }

This ensures only the main artifact for `guava` is resolved, and none of its transitive dependencies will be included.

| Disabling transitive dependency resolution will likely require you to declare the necessary runtime dependencies in your build script which otherwise would have been resolved automatically. Not doing so might lead to runtime classpath issues.

If you want to disable transitive resolution globally across all dependencies, you can set this behavior at the configuration level:

build.gradle.kts

    configurations.configureEach {
        isTransitive = false
    }

    dependencies {
        implementation("com.google.guava:guava:23.0")
    }

build.gradle

    configurations.configureEach {
        transitive = false
    }
    dependencies {
        implementation 'com.google.guava:guava:23.0'
    }

This disables transitive resolution for all dependencies in the project. Be aware that this may require you to manually declare any transitive dependencies that are required at runtime.

For more information, see Configuration.setTransitive(boolean).

## 9. Dependency Resolve Rules and Other Conditionals

Dependency resolve rules are executed for each dependency as it’s being resolved, providing a powerful API to modify a dependency’s attributes—such as group, name, or version—before the resolution is finalized.

This allows for advanced control over dependency resolution, enabling you to substitute one module for another during the resolution process.

This feature is particularly useful for implementing advanced dependency management patterns. With dependency resolve rules, you can redirect dependencies to specific versions or even different modules entirely, allowing you to enforce consistent versions across a project or override problematic dependencies:

build.gradle.kts

    configurations.configureEach {
        resolutionStrategy {
            eachDependency {
                if (requested.group == "com.example" && requested.name == "old-library") {
                    useTarget("com.example:new-library:1.0.0")
                    because("Our license only allows use of version 1")
                }
            }
        }
    }

build.gradle

    configurations.configureEach {
        resolutionStrategy {
            eachDependency {
                if (requested.group == "com.example" && requested.name == "old-library") {
                    useTarget("com.example:new-library:1.0.0")
                    because("Our license only allows use of version 1")
                }
            }
        }
    }

In this example, if a dependency on `com.example:old-library` is requested, it will be substituted with `com.example:new-library:1.0.0` during resolution.

For more advanced usage and additional examples, refer to the ResolutionStrategy class in the API documentation.

### Implementing a custom versioning scheme

In some corporate environments, module versions in Gradle builds are maintained and audited externally. Dependency resolve rules offer an effective way to implement this:

- Developers declare dependencies in the build script using the module’s group and name, but specify a placeholder version like `default`.

- A dependency resolve rule then resolves the `default` version to an approved version, which is retrieved from a corporate catalog of sanctioned modules.

This approach ensures that only approved versions are used, while allowing developers to work with a simplified and consistent versioning scheme.

The rule implementation can be encapsulated in a corporate plugin, making it easy to apply across all projects within the organization:

build.gradle.kts

    configurations.configureEach {
        resolutionStrategy.eachDependency {
            if (requested.version == "default") {
                val version = findDefaultVersionInCatalog(requested.group, requested.name)
                useVersion(version.version)
                because(version.because)
            }
        }
    }

    data class DefaultVersion(val version: String, val because: String)

    fun findDefaultVersionInCatalog(group: String, name: String): DefaultVersion {
        //some custom logic that resolves the default version into a specific version
        return DefaultVersion(version = "1.0", because = "tested by QA")
    }

build.gradle

    configurations.configureEach {
        resolutionStrategy.eachDependency { DependencyResolveDetails details ->
            if (details.requested.version == 'default') {
                def version = findDefaultVersionInCatalog(details.requested.group, details.requested.name)
                details.useVersion version.version
                details.because version.because
            }
        }
    }

    def findDefaultVersionInCatalog(String group, String name) {
        //some custom logic that resolves the default version into a specific version
        [version: "1.0", because: 'tested by QA']
    }

In this setup, whenever a developer specifies `default` as the version, the resolve rule replaces it with the approved version from the corporate catalog.

This strategy ensures compliance with corporate policies while providing flexibility and ease of use for developers. Encapsulating this logic in a plugin also ensures consistency across multiple projects.

### Replacing unwanted dependency versions

Dependency resolve rules offer a powerful mechanism for blocking specific versions of a dependency and substituting them with an alternative.

This is particularly useful when a specific version is known to be problematic—such as a version that introduces bugs or relies on a library that isn’t available in public repositories. By defining a resolve rule, you can automatically replace a problematic version with a stable one.

Consider a scenario where version `1.2` of a library is broken, but version `1.2.1` contains important fixes and should always be used instead. With a resolve rule, you can enforce this substitution: "anytime version `1.2` is requested, it will be replaced with `1.2.1`. Unlike forcing a version, this rule only affects the specific version `1.2`, leaving other versions unaffected:

build.gradle.kts

    configurations.configureEach {
        resolutionStrategy.eachDependency {
            if (requested.group == "org.software" && requested.name == "some-library" && requested.version == "1.2") {
                useVersion("1.2.1")
                because("fixes critical bug in 1.2")
            }
        }
    }

build.gradle

    configurations.configureEach {
        resolutionStrategy.eachDependency { DependencyResolveDetails details ->
            if (details.requested.group == 'org.software' && details.requested.name == 'some-library' && details.requested.version == '1.2') {
                details.useVersion '1.2.1'
                details.because 'fixes critical bug in 1.2'
            }
        }
    }

If version `1.3` is also present in the dependency graph, then even with this rule, Gradle’s default conflict resolution strategy would select `1.3` as the latest version.

Difference from Rich Version Constraints: Using rich version constraints, you can reject certain versions outright, causing the build to fail or select a non-rejected version if a dynamic dependency is used. In contrast, a dependency resolve rule like the one shown here manipulates the version being requested, replacing it with a known good version when a rejected one is found. This approach is a solution for handling rejected versions, while rich version constraints are about expressing the intent to avoid certain versions.

### Lazily influencing resolved dependencies

Plugins can lazily influence dependencies by adding them conditionally or setting preferred versions when no version is specified by the user.

Below are two examples illustrating these use cases.

This example demonstrates how to add a dependency to a configuration based on some condition, evaluated lazily:

build.gradle.kts

    configurations {
        implementation {
            dependencies.addLater(project.provider {
                val dependencyNotation = conditionalLogic()
                if (dependencyNotation != null) {
                    project.dependencies.create(dependencyNotation)
                } else {
                    null
                }
            })
        }
    }

build.gradle

    configurations {
        implementation {
            dependencies.addLater(project.provider {
                def dependencyNotation = conditionalLogic()
                if (dependencyNotation != null) {
                    return project.dependencies.create(dependencyNotation)
                } else {
                    return null
                }
            })
        }
    }

In this case, `addLater` is used to defer the evaluation of the dependency, allowing it to be added only when certain conditions are met.

In this example, the build script sets a preferred version of a dependency, which will be used if no version is explicitly specified:

build.gradle.kts

    dependencies {
        implementation("org:foo")

        // Can indiscriminately be added by build logic
        constraints {
            implementation("org:foo:1.0") {
                version {
                    // Applied to org:foo if no other version is specified
                    prefer("1.0")
                }
            }
        }
    }

build.gradle

    dependencies {
        implementation("org:foo")

        // Can indiscriminately be added by build logic
        constraints {
            implementation("org:foo:1.0") {
                version {
                    // Applied to org:foo if no other version is specified
                    prefer("1.0")
                }
            }
        }
    }

This ensures that `org:foo` uses version `1.0` unless the user specifies another version.