Dependency constraints function similarly to dependencies, with the key distinction that they do not introduce a dependency themselves. Instead, constraints define version requirements that influence the resolution process when a dependency is brought into the project by other means.

Although constraints are not strict versions by default, you can specify a strict version constraint if needed. Once the dependency is included, the version specified by the constraint participates in conflict resolution just as if it were declared as a direct dependency.

When developing a single-project library, constraints can be directly declared alongside direct dependencies. However, when developing multi-project libraries and applications, dependencies are best declared centrally in a platform:

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'
    }
}

In general, dependencies are categorized as either direct or transitive:

  • Direct dependencies are those explicitly specified within a component’s build or metadata.

  • Transitive dependencies are not directly specified; they are pulled in automatically as dependencies of the direct dependencies.

A component may require both direct and transitive dependencies to compile or run.

Declaring constraints alongside direct dependencies

Dependency constraints allow you to define the version or version range for a specific dependency, whenever that dependency is encountered during resolution.

This is the preferred method for managing the version of a component across multiple configurations or projects.

When Gradle resolves a module version, it considers all relevant factors, including rich versions, transitive dependencies, and dependency constraints for that module. The highest version that meets all the conditions is selected. If no such version exists, Gradle will fail with an error, detailing the conflicting declarations.

In such cases, you can adjust your dependency declarations, dependency constraints, or make necessary changes to transitive dependencies.

Like dependency declarations, dependency constraints are scoped by configurations, allowing you to selectively apply them to specific parts of a build.

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"):

    • This line creates a constraint on the api configuration, asserting that if commons-httpclient is ever resolved by a resolvable configuration that extends the api configuration, its version must be 3.1 or higher.

    • If a transitive dependency (a dependency of a dependency) or another module in the project pulls in a different version of commons-httpclient, Gradle enforce the dependency to resolve to at least version 3.1.

    • This constraint ensures that the library commons-httpclient will be at least version 3.1 across all configuration that extend the api configuration.

  2. runtime("org.postgresql:postgresql:42.2.5"):

    • Similarly, this line applies a constraint on the runtime configuration, enforcing that org.postgresql:postgresql must resolve to at least version 42.2.5.

    • Even if other dependencies or modules within the project try to bring in a different version of postgresql, Gradle will choose the higher of 42.2.5 and the other declared versions.

    • This ensures that any runtime dependencies on postgresql will resolve to at least version 42.2.5 across all resolvable configurations that extend the runtime configuration.

Adding constraints on transitive dependencies

Issues with dependency management often arise from transitive dependencies. Developers sometimes mistakenly address these issues by adding direct dependencies instead of handling them properly with constraints.

Dependency constraints allow you to control the selection of transitive dependencies.

In the following example, the version constraint for commons-codec:1.11 applies only when commons-codec is brought in as a transitive dependency since it is not directly declared as a dependency in the project. If commons-codec is not pulled in transitively, the constraint has no effect:

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'
        }
    }
}

Dependency constraints can also define rich version constraints and support strict versions, allowing you to enforce a specific version even if it conflicts with a transitive dependency’s version (e.g., if a downgrade is necessary).

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.

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.