# How to Prevent Accidental or Eager Dependency Upgrades in Gradle

version 9.0.0

This guide explains how to prevent accidental dependency versions due to Gradle’s eager dependency resolution.

## Why Prevent Accidental Dependency Upgrades?

When managing dependencies in Gradle, you may encounter situations where transitive dependencies cause unexpected upgrades to newer versions. This can lead to unintended behavior or compatibility issues. Gradle, by default, performs optimistic upgrades, meaning it resolves to the highest available version when multiple versions of a dependency are found in the dependency graph.

For example, if both `3.1` and `3.2` versions of `commons-lang3` are present, Gradle will select `3.2`, even if your build script explicitly declares `3.1`. If your build uses a feature available in version `3.1` but not `3.2`, or if you haven’t updated your build to be compatible with version `3.2`, you may not want this eager upgrade from Gradle’s dependency resolution process.

build.gradle.kts

    dependencies {
        implementation("org.apache.commons:commons-lang3:3.2")

        constraints {
            implementation("org.apache.commons:commons-lang3:3.1") {
                because("Version 1.3 introduces breaking changes not yet handled")
            }
        }
    }

build.gradle

    dependencies {
        implementation("org.apache.commons:commons-lang3:3.2")

        constraints {
            implementation("org.apache.commons:commons-lang3:3.1") {
                because("Version 1.3 introduces breaking changes not yet handled")
            }
        }
    }

Running `./gradlew dependencies --configuration runtimeClasspath` showcases the results:

dependencies.out

    > Task :dependencies

    ------------------------------------------------------------
    Root project 'how_to_prevent_accidental_upgrades'
    ------------------------------------------------------------

    runtimeClasspath - Compile classpath for source set 'main'.
    +--- org.apache.commons:commons-lang3:3.2
    \--- org.apache.commons:commons-lang3:3.1 -> 3.2 (c)

## Option 1: Enforce Strict Dependency Resolution

Gradle provides an option to fail the build when a version conflict occurs, ensuring that no unintended upgrades happen.

To enable this, configure your build to fail on version conflicts:

build.gradle.kts

    configurations.all {
        resolutionStrategy.failOnVersionConflict()
    }

build.gradle

    configurations.all {
        resolutionStrategy.failOnVersionConflict()
    }

When this setting is enabled, Gradle will stop execution and report an error if multiple versions of a dependency conflict, instead of automatically upgrading to the highest version.

Running `./gradlew dependencies --configuration runtimeClasspath` showcases the failure:

dependencies-fail.out

    > Task :dependencies FAILED

    ------------------------------------------------------------
    Root project 'how_to_prevent_accidental_upgrades'
    ------------------------------------------------------------

    runtimeClasspath - Runtime classpath of source set 'main'.

    FAILURE: Build failed with an exception.

    * What went wrong:
    Execution failed for task ':dependencies'.
    > Could not resolve all dependencies for configuration ':runtimeClasspath'.
       > Conflict found for the following module:
           - org.apache.commons:commons-lang3 between versions 3.2 and 3.1

## Option 2: Using Dependency Constraints

If you have multiple dependencies that use a shared library, you can enforce a consistent version across all modules using dependency constraints:

build.gradle.kts

    dependencies {
        implementation("org.apache.commons:commons-lang3")
        constraints {
            implementation("org.apache.commons:commons-lang3") {
                version {
                    strictly("3.1")
                }
            }
        }
    }

build.gradle

    dependencies {
        implementation("org.apache.commons:commons-lang3")
        constraints {
            implementation("org.apache.commons:commons-lang3") {
                version {
                    strictly("3.1")
                }
            }
        }
    }

With `strictly("3.1")`, Gradle ensures that no other version can override the specified dependency.

Running `./gradlew dependencies --configuration runtimeClasspath` showcases the results:

dependencies-const.out

    > Task :dependencies

    ------------------------------------------------------------
    Root project 'how_to_prevent_accidental_upgrades'
    ------------------------------------------------------------

    runtimeClasspath - Runtime classpath of source set 'main'.
    +--- org.apache.commons:commons-lang3 -> 3.1
    \--- org.apache.commons:commons-lang3:{strictly 3.1} -> 3.1 (c)

## Option 3: Locking Dependencies to Specific Versions

For a more robust solution, you can use dependency locking to ensure Gradle resolves the same dependency versions consistently across builds.

To enable dependency locking:

build.gradle.kts

    configurations.all {
        resolutionStrategy.activateDependencyLocking()
    }

build.gradle

    configurations.all {
        resolutionStrategy.activateDependencyLocking()
    }

Then, generate a lock file:

    ./gradlew dependencies --write-locks

This creates a `gradle.lockfile` that stores the exact dependency versions, preventing future upgrades unless explicitly updated.

## Summary

Gradle’s optimistic dependency resolution may inadvertently upgrade dependencies, causing compatibility issues.

To prevent unexpected upgrades, you can:

- Enforce strict resolution (`failOnVersionConflict`)

- Explicitly constrain dependencies using the `strictly` keyword

- Use dependency locking (`activateDependencyLocking`) to maintain consistent versions across builds.