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
api
configuration, asserting that ifcommons-httpclient
is ever resolved by a resolvable configuration that extends theapi
configuration, its version must be3.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 version3.1
. -
This constraint ensures that the library
commons-httpclient
will be at least version3.1
across all configuration that extend theapi
configuration.
-
-
runtime("org.postgresql:postgresql:42.2.5")
:-
Similarly, this line applies a constraint on the
runtime
configuration, enforcing thatorg.postgresql:postgresql
must 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.5
and the other declared versions. -
This ensures that any runtime dependencies on
postgresql
will resolve to at least version42.2.5
across all resolvable configurations that extend theruntime
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:
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
.