# Preventing accidental dependency upgrades

version 6.5-milestone-1

In some situations, you might want to be in total control of the dependency graph. In particular, you may want to make sure that:

- the versions declared in a build script actually correspond to the ones being resolved

- or make sure that dependency resolution is reproducible over time

Gradle provides ways to perform this by configuring the resolution strategy.

## Failing on version conflict

There’s a version conflict whenever Gradle finds the same module in two different versions in a dependency graph. By default, Gradle performs optimistic upgrades, meaning that if version `1.1` and `1.3` are found in the graph, we resolve to the highest version, `1.3`. However, it is easy to miss that some dependencies are upgraded because of a transitive dependency. In the example above, if `1.1` was a version used in your build script and `1.3` a version brought transitively, you could use `1.3` without actually noticing.

To make sure that you are aware of such upgrades, Gradle provides a mode that can be activated in the resolution strategy of a configuration. Imagine the following dependencies declaration:

Example 1. Direct dependency version not matching a transitive version
build.gradle

    dependencies {
        implementation 'org.apache.commons:commons-lang3:3.0'
        // the following dependency brings lang3 3.8.1 transitively
        implementation 'com.opencsv:opencsv:4.6'
    }

build.gradle.kts

    dependencies {
        implementation("org.apache.commons:commons-lang3:3.0")
        // the following dependency brings lang3 3.8.1 transitively
        implementation("com.opencsv:opencsv:4.6")
    }

Then by default Gradle would upgrade `commons-lang3`, but it is possible to fail the build:

Example 2. Fail on version conflict
build.gradle

    configurations.all {
        resolutionStrategy {
            failOnVersionConflict()
        }
    }

build.gradle.kts

    configurations.all {
        resolutionStrategy {
            failOnVersionConflict()
        }
    }

## Making sure resolution is reproducible

There are cases where dependency resolution can be unstable over time. That is to say that if you build at date D, building at date D+x may give a different resolution result.

This is possible in the following cases:

- dynamic dependency versions are used (version ranges, `latest.release`, `1.+`, …​)

- or changing versions are used (SNAPSHOTs, fixed version with changing contents, …​)

The recommended way to deal with dynamic versions is to use dependency locking. However, it is possible to prevent the use of dynamic versions altogether, which is an alternate strategy:

Example 3. Failing on dynamic versions
build.gradle

    configurations.all {
        resolutionStrategy {
            failOnDynamicVersions()
        }
    }

build.gradle.kts

    configurations.all {
        resolutionStrategy {
            failOnDynamicVersions()
        }
    }

Likewise, it’s possible to prevent the use of changing versions by activating this flag:

Example 4. Failing on changing versions
build.gradle

    configurations.all {
        resolutionStrategy {
            failOnChangingVersions()
        }
    }

build.gradle.kts

    configurations.all {
        resolutionStrategy {
            failOnChangingVersions()
        }
    }

It’s a good practice to fail on changing versions at release time.

Eventually, it’s possible to combine both failing on dynamic versions and changing versions using a single call:

Example 5. Failing on non-reproducible resolution
build.gradle

    configurations.all {
        resolutionStrategy {
            failOnNonReproducibleResolution()
        }
    }

build.gradle.kts

    configurations.all {
        resolutionStrategy {
            failOnNonReproducibleResolution()
        }
    }