Declaring Dependency Constraints
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:
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")
}
}
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:
plugins {
`java-platform`
}
dependencies {
constraints {
api("commons-httpclient:commons-httpclient:3.1")
runtime("org.postgresql:postgresql:42.2.5")
}
}
plugins {
id 'java-platform'
}
dependencies {
constraints {
api 'commons-httpclient:commons-httpclient:3.1'
runtime 'org.postgresql:postgresql:42.2.5'
}
}
-
api("commons-httpclient:commons-httpclient:3.1"):-
This line creates a constraint on the
apiconfiguration, asserting that ifcommons-httpclientis ever resolved by a resolvable configuration that extends theapiconfiguration, its version must be3.1or 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 version3.1. -
This constraint ensures that the library
commons-httpclientwill be at least version3.1across all configuration that extend theapiconfiguration.
-
-
runtime("org.postgresql:postgresql:42.2.5"):-
Similarly, this line applies a constraint on the
runtimeconfiguration, enforcing thatorg.postgresql:postgresqlmust resolve to at least version42.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 of42.2.5and the other declared versions. -
This ensures that any runtime dependencies on
postgresqlwill resolve to at least version42.2.5across all resolvable configurations that extend theruntimeconfiguration.
-
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:
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")
}
}
}
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.