Version declarations (including ranges) only affect the dependencies you declare directly. To control versions of transitive dependencies, use dependency constraints.

dependency management constraints

A dependency constraint sets version requirements for a module without adding that module as a dependency. When the module is pulled in, the constraint participates in version conflict resolution just like a declared dependency version:

  • Constraints are not strict by default (they usually express "at least this version").

  • You can make them strict or use rich versions (e.g., ranges, prefer, reject, strictly) when needed.

Use constraints when you want to control versions centrally and avoid adding extra dependencies just to force a version.

You can declare constraints:

  • Alongside dependencies in a single project (scoped to the same configuration buckets like implementation, runtimeOnly, testImplementation, etc.).

  • Centrally in a platform (recommended for multi-project builds) using the java-platform plugin:

build.gradle.kts
plugins {
    `java-platform`
}

dependencies {
    constraints {
        // Platform declares some versions of libraries used in subprojects
        api("commons-httpclient:commons-httpclient:3.1")
        api("org.apache.commons:commons-lang3:3.8.1")
    }
}
build.gradle
plugins {
    id 'java-platform'
}

dependencies {
    constraints {
        // Platform declares some versions of libraries used in subprojects
        api 'commons-httpclient:commons-httpclient:3.1'
        api 'org.apache.commons:commons-lang3:3.8.1'
    }
}

You cannot declare constraints in version catalogs.

Declaring constraints alongside direct dependencies

Constraints are scoped by bucket configurations (e.g., implementation, runtimeOnly). They apply whenever that dependency is encountered during resolution.

The constraints {} block is used within the dependencies {} block to declare these constraints:

build.gradle.kts
plugins {
    `java-platform`
}

dependencies {
    constraints {
        api("commons-httpclient:commons-httpclient:3.1")
        runtime("org.postgresql:postgresql:42.2.5")
    }
}
build.gradle
plugins {
    id 'java-platform'
}

dependencies {
    constraints {
        api 'commons-httpclient:commons-httpclient:3.1'
        runtime 'org.postgresql:postgresql:42.2.5'
    }
}
  1. api("commons-httpclient:commons-httpclient:3.1"): ensures ≥ 3.1 for api.

  2. runtime("org.postgresql:postgresql:42.2.5"): ensures ≥ 42.2.5 for runtime.

If multiple requirements exist, Gradle picks a version that satisfies all. If none exists, resolution fails with an error describing the conflict.

Adding constraints on transitive dependencies

Use constraints to select transitive modules without introducing them as direct dependencies:

build.gradle.kts
dependencies {
    implementation("org.apache.httpcomponents:httpclient")
    constraints {
        implementation("org.apache.httpcomponents:httpclient:4.5.3") {
            because("previous versions have a bug impacting this application")
        }
        implementation("commons-codec:commons-codec:1.11") {
            because("version 1.9 pulled from httpclient has bugs affecting this application")
        }
    }
}
build.gradle
dependencies {
    implementation('org.apache.httpcomponents:httpclient')
    constraints {
        implementation('org.apache.httpcomponents:httpclient:4.5.3') {
            because('previous versions have a bug impacting this application')
        }
        implementation('commons-codec:commons-codec:1.11') {
            because('version 1.9 pulled from httpclient has bugs affecting this application')
        }
    }
}

If commons-codec isn’t brought in transitively, the constraint is a no-op (it doesn’t add the module). If it is brought in, the constraint guides the version selection.

Rich versions and strict versions for constraints

You can attach rich versions to constraints:

build.gradle.kts
dependencies {
    constraints {
        implementation("com.google.guava:guava") {
            version {
                strictly("33.1.0-jre")
            }
            because("avoid older versions with known issues")
        }
    }
}
build.gradle
dependencies {
    constraints {
        implementation("com.google.guava:guava") {
            version {
                strictly("33.1.0-jre")
            }
            because("avoid older versions with known issues")
        }
    }
}

Transitivity of constraints

Dependency constraints are transitive.

If library A depends on library B, and library B declares a constraint on module C, that constraint will affect the version of module C that library A depends on.

For example, if library A depends on module C version 2, but library B declares a constraint on module C version 3, library A will resolve version 3 of module C.

Publishing constraints

Dependency constraints are only published when using Gradle Module Metadata. This means they are fully supported only when both publishing and consuming modules with Gradle.

If modules are consumed with Maven or Ivy, the constraints may not be preserved.