This plugin reports which of your build's dependencies, plugins, and Gradle itself have newer versions available, in the spirit of the Maven Versions Plugin.
Table of contents
dependencyUpdates task
The recommended way to add the Gradle Versions Plugin to any build is to apply the settings plugin once in the settings script. This approach allows the plugin to report updates for the plugins and buildscript dependencies that the settings script declares, in addition to each project's own plugins, buildscript dependencies, and dependencies. It also automatically covers every subproject in a multi-project build (see Multi-project builds).
"settings.gradle.kts":
plugins {
id("io.github.ben-manes.versions.settings") version "$version"
}
"settings.gradle":
plugins {
id 'io.github.ben-manes.versions.settings' version '$version'
}
[!IMPORTANT] Replace
$versionwith the current release, shown in the badge at the top of this page.
After adding the settings plugin to your build, run the dependencyUpdates task
to get the report of up-to-date and outdated dependencies (see The
dependencyUpdates task):
./gradlew dependencyUpdates
The report prints to the console and is written to
build/dependencyUpdates/report.txt:
------------------------------------------------------------
: Project Dependency Updates (report to plain text file)
------------------------------------------------------------
The following dependencies have later milestone versions:
- com.google.inject:guice [2.0 -> 7.0.0]
https://github.com/google/guice
- org.springframework.boot:spring-boot-dependencies [1.5.8.RELEASE -> 4.1.0]
https://spring.io/projects/spring-boot
Gradle release-candidate updates:
- Gradle: [8.4 -> 9.7.0]
The task is configured in the root project's build script:
"build.gradle.kts":
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
revision = "release"
outputFormatter = "json"
}
"build.gradle":
tasks.named("dependencyUpdates").configure {
revision = 'release'
outputFormatter = 'json'
}
A build with no root build script can configure the task from the settings script instead:
"settings.gradle.kts":
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
gradle.rootProject {
tasks.withType(DependencyUpdatesTask::class.java).configureEach {
revision = "release"
outputFormatter = "json"
}
}
"settings.gradle":
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
gradle.rootProject {
tasks.withType(DependencyUpdatesTask).configureEach {
revision = 'release'
outputFormatter = 'json'
}
}
Every task property is covered in Task properties, which opens with a recommended configuration most builds need: a stability filter so that a pre-release version is not offered as an update, and a bound so that an upgrade the build has ruled out is not offered either.
dependencyUpdates taskDisplays a report of the project dependencies that are up-to-date, exceed the
latest version found, have upgrades, or failed to be resolved. When a dependency
cannot be resolved the exception is logged at the info level.
The report includes dependencies declared through a version catalog, but the plugin only reports—it never edits build files or the catalog. See Related plugins for tools that apply updates automatically.
The report also includes the dependencies that a plugin contributes lazily
rather than the build declaring them, such as the Kotlin standard library and
the tool versions of the jacoco, checkstyle, and pmd plugins. Their
current version is whatever the contributing plugin supplies when the task
runs, so a tool version the build never sets is reported at the default
bundled with Gradle—a version that appears nowhere in the build script. An
extra line is printed under such an entry, so it does not read as a resolution
bug. The line shows the configuration the plugin declared the dependency
against:
- org.jacoco:org.jacoco.ant [0.8.11 -> 0.8.13]
contributed by a plugin into the 'jacocoAnt' configuration
Set the extension's version, such as jacoco.toolVersion, to control what the
report compares against.
A plugin that fills its classpath when it is applied, rather than when the configuration is first resolved, cannot be told apart from the build declaring the dependency. Such an entry shows the configuration the dependency was declared against instead:
- org.jetbrains.kotlin:kotlin-build-tools-impl [2.4.0 -> 2.4.10]
declared in the 'kotlinAbiValidationCompatClasspath' configuration
A configuration the build declares against directly, such as a tool
configuration of its own, is shown the same way. Reject the name an entry
shows with filterDeclaredConfigurations to
leave it out of the report.
Gradle updates are checked for on the current, release-candidate and
nightly release channels. The plain-text report displays Gradle updates as a
separate category in breadcrumb style, excluding nightly builds. The XML and
JSON reports cover all three release channels: whether a release is an update
with respect to the Gradle instance running the build, whether an update check
failed, and a reason field explaining failures or missing information. The
update check may be disabled using the checkForGradleUpdate flag.
To find the latest version of a dependency, the task queries each repository
for the versions available there. Gradle caches the result for 24 hours, so a
version published within the last day may be missing from the report, because
the cached answer predates it. Re-run with --refresh-dependencies to bypass
the cache and query the repositories again:
./gradlew dependencyUpdates --refresh-dependencies
[!TIP] The
--refresh-dependenciesflag applies to the whole build rather than to this task alone, so it also re-checks every other dependency the build resolves and is slower than a normal run. Use it when a release you are expecting does not appear, not routinely.
The properties below each solve a different problem, and most builds end up wanting the same few. This is a complete starting point, assembled from the sections that follow:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
fun String.isNonStable(): Boolean {
val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { uppercase().contains(it) }
val regex = "^[0-9,.v-]+(-r|-jre|-android)?$".toRegex()
val isStable = stableKeyword || regex.matches(this)
return isStable.not()
}
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
checkConstraints = true
rejectVersionIf {
candidate.version.isNonStable() && !currentVersion.isNonStable()
}
}
def isNonStable = { String version ->
def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { it -> version.toUpperCase().contains(it) }
def regex = /^[0-9,.v-]+(-r|-jre|-android)?$/
return !stableKeyword && !(version ==~ regex)
}
tasks.named("dependencyUpdates").configure {
checkConstraints = true
rejectVersionIf {
isNonStable(candidate.version) && !isNonStable(currentVersion)
}
}
checkConstraints adds the versions a constraints block manages to the
report (see Constraints).strictly or reject bound written in the build,
outside a dynamic version declared on the buildscript classpath, or outside
the version fixed by a consumed platform, is already left out by
rejectOutOfBoundVersions, which is on by default (see Respecting declared
bounds).Each piece stands alone: drop any line whose behavior you do not want, and the rest keep working.
The configuration filters are absent only because their arguments are build-specific: the names to reject come from your own report.
In Kotlin the isNonStable extension replaces a helper of that name already in
the build. A top-level fun isNonStable(version: String) compiles to the same
JVM signature, so keeping both fails the build with a platform declaration
clash.
Each property below configures the report or where it is written, and links to the section that describes it. A property taking a predicate or a closure has no command line option, since no command line can express the logic.
| Property | Values | Default | Option |
|---|---|---|---|
revision | release, milestone, integration | milestone | --revision |
gradleReleaseChannel | current, release-candidate, nightly | release-candidate | --gradle-release-channel |
checkForGradleUpdate | true, false | true | --[no-]check-for-gradle-update |
checkConstraints | true, false | false | --[no-]check-constraints |
checkBuildEnvironmentConstraints | true, false | false | --[no-]check-build-environment-constraints |
filterConfigurations | a Spec<Configuration> | every configuration | |
filterDeclaredConfigurations | a Spec<String> | every name | |
rejectOutOfBoundVersions | true, false | true | --[no-]reject-out-of-bound-versions |
rejectVersionIf | a predicate over the candidate | nothing rejected | |
outputFormatter | text, json, xml, html, a comma separated list of those, or a Reporter | text | --output-formatter |
outputDir | a directory path | <buildDirectory>/dependencyUpdates | --output-dir |
reportfileName | a file name, without the extension | report | --report-file-name |
gradleVersionsApiBaseUrl | a URL | https://services.gradle.org/versions/ | --gradle-versions-api-base-url |
cleanLegacyPartials | true, false | false | --[no-]clean-legacy-partials |
The resolutionStrategy block is configured
rather than set to a value, so it is absent from the table above.
Each option in the table above changes a property for a single run, without an edit to the build script. Use one to see what a check leaves out, then return to the configured behavior on the next run:
./gradlew dependencyUpdates --no-check-constraints
--[no-] marks the options that take no argument, so
--no-check-for-gradle-update turns off a check that is enabled in the build
script. Every option, its description, and the values --revision and
--gradle-release-channel accept are printed by gradle help --task dependencyUpdates.
Where more than one is set, the command line option is the one that applies, ahead of a system property and of what is configured in the build.
An option applies within the build it is invoked in. Where a report merges an included build, that build's rows are resolved with its own configuration, since the option is not passed to the included build. A system property is, being set for the whole JVM.
Two properties leave things out of the report. filterConfigurations
controls which configurations the task checks: a rejected configuration is
never resolved, and nothing reachable only through it is reported.
filterDeclaredConfigurations controls which entries stay in the report once
the checks have run, matched against the configuration name printed on the
entry itself.
filterConfigurationsThe task checks every resolvable configuration of every project. A build that only acts on its main classpaths can restrict the check to them:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
filterConfigurations = Spec<Configuration> {
it.name == "runtimeClasspath" || it.name == "compileClasspath"
}
}
tasks.named("dependencyUpdates").configure {
filterConfigurations {
it.name == "runtimeClasspath" || it.name == "compileClasspath"
}
}
A dependency is left out of the report once every configuration that reaches
it is rejected, and so is everything reachable only through a rejected
configuration—rejecting compileClasspath removes the build's own
dependencies too. Reach for this filter when a whole configuration is noise:
a skipped configuration also costs no version lookups, which suits the
classpaths a plugin fills for its own tooling, at versions the build never
chose. The Kotlin Gradle Plugin and
Android Gradle Plugin sections below give ready
sets.
filterDeclaredConfigurationsStart from the report line. Rejecting the name an entry shows removes the entry:
- org.jacoco:org.jacoco.ant [0.8.11 -> 0.8.13]
contributed by a plugin into the 'jacocoAnt' configuration
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
filterDeclaredConfigurations = Spec<String> { it != "jacocoAnt" }
}
tasks.named("dependencyUpdates").configure {
filterDeclaredConfigurations { it != "jacocoAnt" }
}
It matches exactly the name printed on the entry, from a "contributed by a
plugin into" line or a "declared in" one—a tool configuration the build
declares against directly is rejectable the same way. An entry is left out of
the report when every name it shows is rejected, and an entry printed with no
configuration name is never affected: dependencies declared through
implementation and its siblings are printed without one, so no rejection
applies to them, and where such a declaration also backs a named entry, the
rejection removes the attribution line rather than the dependency.
Both filters silence a tooling configuration like the KGP and AGP sets
below; prefer filterConfigurations there, since it also skips the lookups.
The two differ when the name an entry shows is not one the task checks (see
The dependencyUpdates task): a declarable
configuration read through a resolvable classpath that extends it, and
implementation with a plugin's contribution in it, both show a name that
filterConfigurations cannot match. filterDeclaredConfigurations matches
the name shown, with no side effect on what is checked. Buildscript and
settings classpath entries are ordinarily printed with no configuration name,
so neither property affects them; an entry a plugin contributes shows
classpath and can be rejected like any other entry.
The Kotlin Gradle Plugin fills four fixed classpaths for its own tooling,
plus one kotlinCompilerPluginClasspath<SourceSet> per source set—a JVM
test suite
adds one too—so a list of names goes stale as the build grows. Match the
family by prefix; at KGP 2.4.10:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
fun isKgpInternal(configurationName: String): Boolean {
val kgpInternalConfigurations = setOf(
"kotlinCompilerClasspath",
"kotlinBuildToolsApiClasspath",
"kotlinAbiValidationCompatClasspath",
"kotlinKlibCommonizerClasspath",
)
return configurationName in kgpInternalConfigurations ||
(configurationName.startsWith("kotlinCompilerPluginClasspath") &&
configurationName != "kotlinCompilerPluginClasspath")
}
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
filterConfigurations = Spec<Configuration> { !isKgpInternal(it.name) }
}
def isKgpInternal = { String configurationName ->
def kgpInternalConfigurations = [
"kotlinCompilerClasspath",
"kotlinBuildToolsApiClasspath",
"kotlinAbiValidationCompatClasspath",
"kotlinKlibCommonizerClasspath",
]
return kgpInternalConfigurations.contains(configurationName) ||
(configurationName.startsWith("kotlinCompilerPluginClasspath") &&
configurationName != "kotlinCompilerPluginClasspath")
}
tasks.named("dependencyUpdates").configure {
filterConfigurations { !isKgpInternal(it.name) }
}
The prefix stops short of the unsuffixed kotlinCompilerPluginClasspath,
which is still checked, and stays narrower than dropping everything that
starts with kotlin. A compiler plugin the build itself declares resolves
through these same suffixed classpaths and is filtered with them. Its Gradle
plugin's marker is still reported, so a version shared between the two stays
visible, but a compiler plugin versioned apart from its Gradle plugin is left
out with nothing in its place—keep the classpath it resolves through if you
declare one.
The Android Gradle Plugin fills a set of its own. At AGP 9.3.1, which builds
in its Kotlin support rather than applying a separate Kotlin plugin, that is
androidLintTool, two of the Kotlin classpaths above, and a
unified-test-platform-* family thirteen configurations wide. Match that
family by prefix as well: which configurations it contains changes between AGP
releases, and no configuration a build fills itself starts with that prefix.
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
val agpInternal = setOf(
"androidLintTool",
"kotlinBuildToolsApiClasspath",
"kotlinCompilerClasspath",
)
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
filterConfigurations = Spec<Configuration> {
it.name !in agpInternal && !it.name.startsWith("unified-test-platform-")
}
}
def agpInternal = [
"androidLintTool",
"kotlinBuildToolsApiClasspath",
"kotlinCompilerClasspath",
]
tasks.named("dependencyUpdates").configure {
filterConfigurations {
!agpInternal.contains(it.name) &&
!it.name.startsWith("unified-test-platform-")
}
}
These names come from the plugins at the versions above, not from Gradle, and they change between plugin releases. Take the set from your own report rather than from this page, and keep the matches exact wherever a plugin's configurations share a prefix with ones the build fills itself.
If you
manage
transitive dependency versions with a constraints block, you can enable
checking of constraints by specifying the checkConstraints attribute of the
dependencyUpdates task. If you want to check external constraints (defined in
init scripts or by Gradle itself) you can do so by specifying the
checkBuildEnvironmentConstraints attribute of the dependencyUpdates task.
The attribute covers the constraints a project declares, on its own
configurations or on ones they extend. A project applying the
java-platform
plugin to define a BOM declares its constraints that way, so running the task on
the platform project reports them.
A project that consumes a platform does not declare that platform's
constraints, under any of platform("group:artifact:version"),
enforcedPlatform, or platform(project(":platform")). Gradle supplies those
versions to the consumer as resolution metadata, which checkConstraints does
not read, so they are not enumerated in the consumer's report. This does not
affect the modules the consumer declares itself: a versionless declaration whose
version the platform supplies is reported and offered updates as usual. Only a
module the consumer never declares is absent, and those appear in the platform
project's own report.
The platform a platform project imports is reported here, though. A build whose
platform project declares api(platform("group:artifact:version")) reaches that
BOM's constraints, but the BOM itself is declared nowhere the report covers, and
a platform in an included build is further out of reach, since that build is
reported separately (see Composite builds). With
checkConstraints an entry for the BOM is printed beside the versionless
declarations whose versions it supplies, so the coordinate to bump appears in
the report. Where the build declares a version for every module, the walk goes
no further than the platform project itself. It also stops at the first
published platform: a BOM imported by that BOM reflects the BOM's version rather
than the build's, and a BOM that arrives as a library's resolution metadata was
never imported by the build, so neither is reported. Every platform project
that imports the BOM is printed on the BOM's entry, by build tree path.
The platform behind a constrained module's version is included in that module's
own entry either way, as constrained by the platform :platform for a platform
project included in the build and constrained by the platform group:artifact
for a BOM, whose own version appears on its own entry. The platform is only
printed when its constraint is the same version the entry shows. If something
else in the build required a higher version and won out, changing the platform
would not change that version, so nothing is printed. The coordinate to bump
appears on the entry where the version is stated (see
Respecting declared bounds). This line does not
depend on checkConstraints. The same names are present in the JSON and XML
reports as constrainedBy, beside the platformProjects importers of a
platform's own entry.
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
checkConstraints = true
checkBuildEnvironmentConstraints = true
}
tasks.named("dependencyUpdates").configure {
checkConstraints = true
checkBuildEnvironmentConstraints = true
}
The revision task property controls the Ivy resolution
strategy
for determining what constitutes the latest version of a dependency. Maven's
dependency metadata does not distinguish between milestone and release versions.
The following strategies are natively supported by Gradle:
The strategy can be specified either on the task or as a system property for ad hoc usage:
./gradlew dependencyUpdates -Drevision=release
Because Maven repositories do not mark pre-release versions, an alpha or release candidate can still appear as the latest version under any revision. To only be offered stable updates, reject pre-release candidates (see Filtering unstable versions).
To further control which versions are accepted, define what counts as an unstable version. There is no agreed standard, but this is a good starting point:
fun String.isNonStable(): Boolean {
val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { uppercase().contains(it) }
val regex = "^[0-9,.v-]+(-r|-jre|-android)?$".toRegex()
val isStable = stableKeyword || regex.matches(this)
return isStable.not()
}
def isNonStable = { String version ->
def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { it -> version.toUpperCase().contains(it) }
def regex = /^[0-9,.v-]+(-r|-jre|-android)?$/
return !stableKeyword && !(version ==~ regex)
}
The trailing -jre and -android keep Guava's ordinary releases out of the
unstable set. A version that spells its qualifier some other way, such as
13.4.0.jre11, still matches as unstable and needs the pattern extended, so
check this against the versions in your own build before relying on it.
You can then configure Component Selection
Rules.
The current version of a component can be retrieved with the currentVersion
property. You can either use the simplified syntax rejectVersionIf { ... } or
configure a complete resolution strategy. Multiple registrations compose, so a
candidate is rejected if any registered filter rejects it.
Example 1: reject all non-stable versions
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
rejectVersionIf {
candidate.version.isNonStable()
}
}
Example 2: disallow release candidates as upgradable versions from stable versions
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
rejectVersionIf {
candidate.version.isNonStable() && !currentVersion.isNonStable()
}
}
Example 3: using the full syntax
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
resolutionStrategy {
componentSelection {
all {
if (candidate.version.isNonStable() && !currentVersion.isNonStable()) {
reject("Release candidate")
}
}
}
}
}
Example 4: disallow candidates less mature than the current version, so an -rc
keeps being offered -rc updates but never a -beta
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
val qualifiers = listOf("preview", "alpha", "beta", "m", "cr", "rc") // order is important
fun maturityLevel(version: String): Int {
val index = qualifiers.indexOfFirst {
version.matches(".*[.\\-]$it[.\\-\\d]*".toRegex(RegexOption.IGNORE_CASE))
}
return if (index < 0) qualifiers.size else index
}
rejectVersionIf {
maturityLevel(candidate.version) < maturityLevel(currentVersion)
}
}
Example 1: reject all non-stable versions
tasks.named("dependencyUpdates").configure {
rejectVersionIf {
isNonStable(candidate.version)
}
}
Example 2: disallow release candidates as upgradable versions from stable versions
tasks.named("dependencyUpdates").configure {
rejectVersionIf {
isNonStable(candidate.version) && !isNonStable(currentVersion)
}
}
Example 3: using the full syntax
tasks.named("dependencyUpdates").configure {
resolutionStrategy {
componentSelection {
all {
if (isNonStable(candidate.version) && !isNonStable(currentVersion)) {
reject('Release candidate')
}
}
}
}
}
Example 4: disallow candidates less mature than the current version, so an -rc
keeps being offered -rc updates but never a -beta
tasks.named("dependencyUpdates").configure {
def qualifiers = ['preview', 'alpha', 'beta', 'm', 'cr', 'rc'] // order is important
def maturityLevel = { String version ->
def index = qualifiers.findIndexOf { version ==~ /(?i).*[.\-]$it[.\-\d]*/ }
return (index < 0) ? qualifiers.size() : index
}
rejectVersionIf {
maturityLevel(candidate.version) < maturityLevel(currentVersion)
}
}
The report is held to the bounds written in the build. A candidate outside a
bound on the declaration, or outside the version fixed by a consumed platform,
is left out, so the only versions listed are ones the build can actually be
moved to. Set rejectOutOfBoundVersions to false to turn that off:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
rejectOutOfBoundVersions = false
}
tasks.named("dependencyUpdates").configure {
rejectOutOfBoundVersions = false
}
To see what is left out, without an edit to the build script, run
./gradlew dependencyUpdates --no-reject-out-of-bound-versions. Run it to
learn what a bounded module could be moved to if the bound were lifted, such
as whether a fix was released for the module ahead of its platform.
A bound is read the way dependency resolution reads it, so strictly "[5.3, 6[" admits 5.3.26 and excludes 6.0.1, and reject "[3.0,)" excludes
everything from 3.0 up. Only strictly and reject bound a candidate: a
require version is a floor resolution may rise above, a range included, and a
prefer version only breaks a tie, so a plain
implementation("group:name:1.2.3") is not bounded.
The bound is not applied in two cases where it makes no difference. A candidate no newer than the version in use is not an upgrade, so it stays, and a module at a version above everything published is still listed on the exceeded row. Where the version in use already lies outside the bound, the bound is not applied at all: once a transitive requirement has pushed the version past a platform, the platform no longer bounds anything.
The same bound is readable from a rule. The constraint written on a
declaration is available as versionConstraint, Gradle's
VersionConstraint.
For a module declared without a version, the constraints fixed for that module
by the consumed platforms are available as platformVersionConstraints. It
is a list rather than a single value, because more than one consumed platform
can bound the same module. The query that finds candidates is deliberately
unbounded, so a rule that applies a declared bound reads it from
versionConstraint rather than restating it. It is null for a module no
declaration was matched to, such as one a substitution rule resolved to, so
guard for that. satisfiesDeclaredBound, the verdict a rule applied before the
property did, is deprecated and will be removed in a later release.
The buildscript classpath is the exception. There a dynamic required version
bounds the candidate, so a plugin declared as version "[1.0, 2[" or
version "1.+" is not offered 2.0, whether the version was written in the
plugins block or came from a version catalog alias. Gradle flattens an alias to
a bare required version on the marker it synthesizes, leaving the two forms
identical, and a plugin has one declaration, so the interval is the version
declared rather than a floor something else may push past. Where a transitive
requirement on a classpath has pushed the selection past the interval, the
version already resolved lies outside the bound, and the bound is not applied,
as above.
A module declared without a version is additionally bound by the version a
consumed platform sets for it, since the build cannot take that upgrade without
also bumping the platform. Given a platform BOM that pins
log4j-core to 2.16.0:
dependencies {
api(platform("org.apache.logging.log4j:log4j-bom:2.16.0"))
api("org.apache.logging.log4j:log4j-core")
}
dependencies {
api platform('org.apache.logging.log4j:log4j-bom:2.16.0')
api 'org.apache.logging.log4j:log4j-core'
}
log4j-core is bounded at 2.16.0 even though versionConstraint is empty
for it, because the bound comes from the platform instead:
The following dependencies are using the latest milestone version:
- org.apache.logging.log4j:log4j-core:2.16.0
constrained by the platform org.apache.logging.log4j:log4j-bom
The following dependencies have later milestone versions:
- org.apache.logging.log4j:log4j-bom [2.16.0 -> 2.17.0]
The platform behind the bound appears on an attribution line under the bounded
entry, so the reason for the version shows up next to it. A platform project
appears as its build tree path, constrained by the platform :platform, and a
BOM as its group and module; where several platforms bound the module, all of
them follow constrained by the platforms. A constraint written as a range is
left out, since a range alone does not identify which version was selected.
The platform still has an entry of its own, so the upgrade that is actually
available, bumping the BOM, is still printed. A build that centralizes its
platforms in a platform project of its own, including one belonging to an
included build, declares the BOM somewhere this report does not cover. With
checkConstraints that BOM is reported here anyway, so the coordinate to bump
is printed either way (see Constraints). A bound the build
declares on the platform itself applies to that entry too: where a BOM's own
version is strictly "[2.0, 3.0[", inline or through a version catalog, the
newest version inside that range is reported and never the major beyond it.
The bound is deliberately narrow:
strictly range.prefer alone does not bound, the same as
a declared prefer.enforcedPlatform's own version into a strictly, so the
rule bounds the platform itself at that version and a newer BOM stops being
offered; a plain platform keeps its own upgrade line.Three things to know before writing a rule against a declared bound. Rejecting
every candidate for a module, including the version it currently resolves to,
does not fail the build: the module is reported in the unresolved section
instead, and its upgrade line with it. A bound on a version that was never
published has that effect, so a strictly "1.2.3" matching nothing is reported
as a resolution failure rather than as up to date.
A resolutionStrategy that throws while the plugin applies it to a
configuration, rather than while Gradle resolves the graph, skips that
configuration instead: its dependencies are absent from every section, the
skipped configuration is listed in the report's skipped section along with
the failure, and a warning is printed to the console. The build still succeeds.
Narrowing the candidate set can also surface metadata that the unbounded query
skipped over, with the same result. And where a module's only declaration is a
constraint, that constraint is reported as its current version, so
currentVersion may read [2.0, 3.1[ rather than a resolved version.
The declared strictVersion, requiredVersion, preferredVersion and
rejectedVersions remain available on versionConstraint for rules that need
something other than the bound.
The gradleReleaseChannel task property controls which release channel of the
Gradle project is used to check for available Gradle updates. Options are:
currentrelease-candidatenightlyThe default is release-candidate. The value can be changed as shown below:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
gradleReleaseChannel = "current"
}
tasks.named("dependencyUpdates").configure {
gradleReleaseChannel = "current"
}
The gradleVersionsApiBaseUrl task property provides an option for
customization of the Gradle versions service URL. If not specified, the default
value https://services.gradle.org/versions/ is used. The customization can be
useful in restricted environments without direct internet access and proxy
availability.
The dependencyUpdates task takes several optional parameters to adjust its
behavior. For an ad hoc run, change one with its command line
option (e.g. --revision release). The revision,
gradleReleaseChannel, outputFormatter, outputDir, and reportfileName
properties may also be set as system properties (e.g. -Drevision=release),
which predate the options and are kept for the builds that use them; where both
are set, the option is the one that applies:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
checkForGradleUpdate = true
outputFormatter = "json"
outputDir = "build/dependencyUpdates"
reportfileName = "report"
}
tasks.named("dependencyUpdates").configure {
checkForGradleUpdate = true
outputFormatter = "json"
outputDir = "build/dependencyUpdates"
reportfileName = "report"
}
checkForGradleUpdateWhether the report includes the Gradle releases the build could upgrade to, which are read from the Gradle versions API over the network for each release channel consulted. Turning it off skips those requests, which suits a build that runs offline or in a restricted environment:
./gradlew dependencyUpdates --no-check-for-gradle-update
outputDirThe directory the report file is written into. A relative path is resolved
against the project directory and an absolute one is used as given. The default
is dependencyUpdates under the project's build directory, which is
build/dependencyUpdates in a standard layout.
reportfileNameThe report file's name, without an extension. Each formatter supplies its own,
so report becomes report.txt, report.json, report.xml or report.html.
Naming more than one formatter writes one file per format, all sharing this
name.
The task property outputFormatter controls the report output format. The
following values are supported:
"plain": format output file as plain text (default)"json": format output file as json text"xml": format output file as xml text, can be used by other plugins (e.g. sonar)"html": format output file as htmlClosure: will be called with the result of the dependency update analysis
(from Kotlin, use the outputFormatter(Action<Result>) function instead)The console summary is printed at the lifecycle log level, so --quiet suppresses
it. The report file is still written; read it, or drop --quiet, if a script was
piping the console output.
You can also set multiple output formats using comma as the separator:
./gradlew dependencyUpdates -Drevision=release -DoutputFormatter=json,xml,html
The task property outputDir controls the output directory for the report
file(s). The directory will be created if it does not exist. The default value
is set to build/dependencyUpdates
./gradlew dependencyUpdates -Drevision=release -DoutputFormatter=json -DoutputDir=/any/path/with/permission
Last the property reportfileName sets the filename (without extension) of the
generated report. It defaults to report. The extension will be set according
to the used output format.
./gradlew dependencyUpdates -Drevision=release -DoutputFormatter=json -DreportfileName=myCustomReport
Sample output in each format:
------------------------------------------------------------
: Project Dependency Updates (report to plain text file)
------------------------------------------------------------
The following dependencies are using the latest milestone version:
- backport-util-concurrent:backport-util-concurrent:3.1
- backport-util-concurrent:backport-util-concurrent-java12:3.1
- io.github.ben-manes:gradle-versions-plugin:0.55.0
The following dependencies exceed the version found at the milestone revision level:
- com.google.guava:guava-tests [99.0-SNAPSHOT <- 23.3-jre]
https://github.com/google/guava
The following dependencies have later milestone versions:
- com.google.guava:guava [15.0 -> 23.0]
https://github.com/google/guava
- com.google.inject:guice [2.0 -> 7.0.0]
https://github.com/google/guice
- com.google.inject.extensions:guice-multibindings [2.0 -> 4.2.3]
https://github.com/google/guice
- com.linecorp.armeria:armeria [0.90.0 -> 1.40.0]
https://armeria.dev/
- io.zipkin.brave:brave [5.7.0 -> 6.3.1]
https://github.com/openzipkin/brave/brave
- org.springframework.boot:spring-boot-dependencies [1.5.8.RELEASE -> 4.1.0]
https://spring.io/projects/spring-boot
Failed to compare versions for the following dependencies because they were declared without version:
- com.google.code.gson:gson
Failed to determine the latest version for the following dependencies (use --info for details):
- com.github.ben-manes:unresolvable:1.0
- com.github.ben-manes:unresolvable2:1.0
- com.google.guava:guava:15.0
https://github.com/google/guava
- dom4j:dom4j
Gradle release-candidate updates:
- Gradle: [8.4 -> 9.7.0]
Alternatively, the report may be output to a structured file.
{
"count": 15,
"current": {
"count": 3,
"dependencies": [
{
"group": "backport-util-concurrent",
"name": "backport-util-concurrent",
"version": "3.1",
"projectUrl": "http://backport-jsr166.sourceforge.net/",
"userReason": null
},
{
"group": "backport-util-concurrent",
"name": "backport-util-concurrent-java12",
"version": "3.1",
"projectUrl": "http://backport-jsr166.sourceforge.net/",
"userReason": null
},
{
"group": "io.github.ben-manes",
"name": "gradle-versions-plugin",
"version": "0.55.0",
"projectUrl": null,
"userReason": null
}
]
},
"outdated": {
"count": 6,
"dependencies": [
{
"group": "com.google.guava",
"name": "guava",
"version": "15.0",
"projectUrl": "https://github.com/google/guava",
"userReason": null,
"available": {
"release": null,
"milestone": "23.0",
"integration": null
}
},
{
"group": "com.google.inject",
"name": "guice",
"version": "2.0",
"projectUrl": "https://github.com/google/guice",
"userReason": null,
"available": {
"release": null,
"milestone": "7.0.0",
"integration": null
}
},
{
"group": "com.google.inject.extensions",
"name": "guice-multibindings",
"version": "2.0",
"projectUrl": "https://github.com/google/guice",
"userReason": null,
"available": {
"release": null,
"milestone": "4.2.3",
"integration": null
}
},
{
"group": "com.linecorp.armeria",
"name": "armeria",
"version": "0.90.0",
"projectUrl": "https://armeria.dev/",
"userReason": null,
"available": {
"release": null,
"milestone": "1.40.0",
"integration": null
}
},
{
"group": "io.zipkin.brave",
"name": "brave",
"version": "5.7.0",
"projectUrl": "https://github.com/openzipkin/brave/brave",
"userReason": null,
"available": {
"release": null,
"milestone": "6.3.1",
"integration": null
}
},
{
"group": "org.springframework.boot",
"name": "spring-boot-dependencies",
"version": "1.5.8.RELEASE",
"projectUrl": "https://spring.io/projects/spring-boot",
"userReason": null,
"available": {
"release": null,
"milestone": "4.1.0",
"integration": null
}
}
]
},
"exceeded": {
"count": 1,
"dependencies": [
{
"group": "com.google.guava",
"name": "guava-tests",
"version": "99.0-SNAPSHOT",
"projectUrl": "https://github.com/google/guava",
"userReason": null,
"latest": "23.3-jre"
}
]
},
"undeclared": {
"count": 1,
"dependencies": [
{
"group": "com.google.code.gson",
"name": "gson",
"version": null,
"projectUrl": null,
"userReason": null
}
]
},
"unresolved": {
"count": 4,
"dependencies": [
{
"group": "com.github.ben-manes",
"name": "unresolvable",
"version": "1.0",
"projectUrl": null,
"userReason": null,
"reason": "Could not find any matches for com.github.ben-manes:unresolvable:+ as no versions of com.github.ben-manes:unresolvable are available.\nSearched in the following locations:\n - https://repo.maven.apache.org/maven2/com/github/ben-manes/unresolvable/maven-metadata.xml"
},
{
"group": "com.github.ben-manes",
"name": "unresolvable2",
"version": "1.0",
"projectUrl": null,
"userReason": null,
"reason": "Could not find any matches for com.github.ben-manes:unresolvable2:+ as no versions of com.github.ben-manes:unresolvable2 are available.\nSearched in the following locations:\n - https://repo.maven.apache.org/maven2/com/github/ben-manes/unresolvable2/maven-metadata.xml"
},
{
"group": "com.google.guava",
"name": "guava",
"version": "15.0",
"projectUrl": "https://github.com/google/guava",
"userReason": null,
"reason": "Could not resolve com.google.guava:guava:+."
},
{
"group": "dom4j",
"name": "dom4j",
"version": "none",
"projectUrl": null,
"userReason": null,
"reason": "Could not resolve dom4j:dom4j:+."
}
]
},
"gradle": {
"enabled": true,
"running": {
"isFailure": false,
"isUpdateAvailable": false,
"reason": "",
"version": "8.4"
},
"current": {
"isFailure": false,
"isUpdateAvailable": true,
"reason": "",
"version": "9.7.0"
},
"releaseCandidate": {
"isFailure": false,
"isUpdateAvailable": false,
"reason": "update check succeeded: no release available",
"version": ""
},
"nightly": {
"isFailure": false,
"isUpdateAvailable": false,
"reason": "update check disabled",
"version": ""
}
},
"skipped": {
"count": 1,
"configurations": [
{
"project": ":",
"name": "compileClasspath",
"reason": "org.gradle.api.InvalidUserCodeException: Could not add a component selection rule for module 'com.google.guava'."
}
]
}
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
<count>15</count>
<current>
<count>3</count>
<dependencies>
<dependency>
<group>backport-util-concurrent</group>
<name>backport-util-concurrent</name>
<version>3.1</version>
<projectUrl>http://backport-jsr166.sourceforge.net/</projectUrl>
</dependency>
<dependency>
<group>backport-util-concurrent</group>
<name>backport-util-concurrent-java12</name>
<version>3.1</version>
<projectUrl>http://backport-jsr166.sourceforge.net/</projectUrl>
</dependency>
<dependency>
<group>io.github.ben-manes</group>
<name>gradle-versions-plugin</name>
<version>0.55.0</version>
</dependency>
</dependencies>
</current>
<outdated>
<count>6</count>
<dependencies>
<outdatedDependency>
<group>com.google.guava</group>
<name>guava</name>
<version>15.0</version>
<projectUrl>https://github.com/google/guava</projectUrl>
<available>
<milestone>23.0</milestone>
</available>
</outdatedDependency>
<outdatedDependency>
<group>com.google.inject</group>
<name>guice</name>
<version>2.0</version>
<projectUrl>https://github.com/google/guice</projectUrl>
<available>
<milestone>7.0.0</milestone>
</available>
</outdatedDependency>
<outdatedDependency>
<group>com.google.inject.extensions</group>
<name>guice-multibindings</name>
<version>2.0</version>
<projectUrl>https://github.com/google/guice</projectUrl>
<available>
<milestone>4.2.3</milestone>
</available>
</outdatedDependency>
<outdatedDependency>
<group>com.linecorp.armeria</group>
<name>armeria</name>
<version>0.90.0</version>
<projectUrl>https://armeria.dev/</projectUrl>
<available>
<milestone>1.40.0</milestone>
</available>
</outdatedDependency>
<outdatedDependency>
<group>io.zipkin.brave</group>
<name>brave</name>
<version>5.7.0</version>
<projectUrl>https://github.com/openzipkin/brave/brave</projectUrl>
<available>
<milestone>6.3.1</milestone>
</available>
</outdatedDependency>
<outdatedDependency>
<group>org.springframework.boot</group>
<name>spring-boot-dependencies</name>
<version>1.5.8.RELEASE</version>
<projectUrl>https://spring.io/projects/spring-boot</projectUrl>
<available>
<milestone>4.1.0</milestone>
</available>
</outdatedDependency>
</dependencies>
</outdated>
<exceeded>
<count>1</count>
<dependencies>
<exceededDependency>
<group>com.google.guava</group>
<name>guava-tests</name>
<version>99.0-SNAPSHOT</version>
<projectUrl>https://github.com/google/guava</projectUrl>
<latest>23.3-jre</latest>
</exceededDependency>
</dependencies>
</exceeded>
<undeclared>
<count>1</count>
<dependencies>
<dependency>
<group>com.google.code.gson</group>
<name>gson</name>
</dependency>
</dependencies>
</undeclared>
<unresolved>
<count>4</count>
<dependencies>
<unresolvedDependency>
<group>com.github.ben-manes</group>
<name>unresolvable</name>
<version>1.0</version>
<reason>Could not find any matches for com.github.ben-manes:unresolvable:+ as no versions of com.github.ben-manes:unresolvable are available.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/com/github/ben-manes/unresolvable/maven-metadata.xml</reason>
</unresolvedDependency>
<unresolvedDependency>
<group>com.github.ben-manes</group>
<name>unresolvable2</name>
<version>1.0</version>
<reason>Could not find any matches for com.github.ben-manes:unresolvable2:+ as no versions of com.github.ben-manes:unresolvable2 are available.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/com/github/ben-manes/unresolvable2/maven-metadata.xml</reason>
</unresolvedDependency>
<unresolvedDependency>
<group>com.google.guava</group>
<name>guava</name>
<version>15.0</version>
<projectUrl>https://github.com/google/guava</projectUrl>
<reason>Could not resolve com.google.guava:guava:+.</reason>
</unresolvedDependency>
<unresolvedDependency>
<group>dom4j</group>
<name>dom4j</name>
<version>none</version>
<reason>Could not resolve dom4j:dom4j:+.</reason>
</unresolvedDependency>
</dependencies>
</unresolved>
<skipped>
<count>1</count>
<configurations>
<skippedConfiguration>
<project>:</project>
<name>compileClasspath</name>
<reason>org.gradle.api.InvalidUserCodeException: Could not add a component selection rule for module 'com.google.guava'.</reason>
</skippedConfiguration>
</configurations>
</skipped>
<gradle>
<enabled>true</enabled>
<running>
<version>8.4</version>
<isUpdateAvailable>false</isUpdateAvailable>
<isFailure>false</isFailure>
<reason/>
</running>
<current>
<version>9.7.0</version>
<isUpdateAvailable>true</isUpdateAvailable>
<isFailure>false</isFailure>
<reason/>
</current>
<releaseCandidate>
<version/>
<isUpdateAvailable>false</isUpdateAvailable>
<isFailure>false</isFailure>
<reason>update check succeeded: no release available</reason>
</releaseCandidate>
<nightly>
<version/>
<isUpdateAvailable>false</isUpdateAvailable>
<isFailure>false</isFailure>
<reason>update check disabled</reason>
</nightly>
</gradle>
</response>
If you need to create a report in a custom format, you can provide a formatter
function to the dependencyUpdates task's outputFormatter. The formatter
receives the analysis as an instance of
com.github.benmanes.gradle.versions.reporter.result.Result:
in the Kotlin DSL it is the receiver of the formatter block, and in Groovy it is
passed as the closure argument.
[!IMPORTANT] Under the configuration cache, the formatter cannot reach the project or the build script from inside the closure, because it runs at execution time. Read what it needs into local variables beforehand, as shown in Migrating from prior versions. Gradle's configuration cache requirements cover the underlying rules.
For example, if you wanted to create an html table for the upgradable dependencies, you could use:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
outputFormatter {
val updatable = outdated.dependencies
if (updatable.isNotEmpty()) {
val table = buildString {
appendLine("<table>")
appendLine(" <thead>")
appendLine(" <tr><td>Group</td><td>Module</td><td>Current version</td><td>Latest version</td></tr>")
appendLine(" </thead>")
appendLine(" <tbody>")
updatable.forEach { dependency ->
appendLine(
" <tr><td>${dependency.group}</td><td>${dependency.name}</td>" +
"<td>${dependency.version}</td>" +
"<td>${dependency.available.release ?: dependency.available.milestone}</td></tr>"
)
}
appendLine(" </tbody>")
appendLine("</table>")
}
println(table)
}
}
}
tasks.named("dependencyUpdates").configure {
outputFormatter = { result ->
def updatable = result.outdated.dependencies
if (!updatable.isEmpty()) {
def table = new StringBuilder()
table.append("<table>\n")
table.append(" <thead>\n")
table.append(" <tr><td>Group</td><td>Module</td><td>Current version</td><td>Latest version</td></tr>\n")
table.append(" </thead>\n")
table.append(" <tbody>\n")
updatable.each { dependency ->
table.append(" <tr><td>${dependency.group}</td><td>${dependency.name}</td>")
table.append("<td>${dependency.version}</td>")
table.append("<td>${dependency.available.release ?: dependency.available.milestone}</td></tr>\n")
}
table.append(" </tbody>\n")
table.append("</table>")
println table
}
}
}
Running the task in the root project generates one merged report covering every project. The report is aggregated from a task in each project, so it works with parallel execution, the configuration cache, and configure on demand. Under the configuration cache the dependency metadata read during resolution is tracked as an input, so a newly published version invalidates the cached entry rather than serving a stale report.
The merged report and the partial results it merges are written to the
aggregating project's build directory. clean removes them only if that project
has a clean task, which the base plugin supplies. A root project that
applies no other plugin can add base for that alone; the task itself does not
need it.
"build.gradle.kts":
plugins {
base
}
"build.gradle":
plugins {
id 'base'
}
When a coordinate's declared version differs across the aggregated projects, the projects that declared each version are printed in the plain text, JSON, XML, and HTML reports, so the entry no longer reads as self-contradictory:
The following dependencies have later milestone versions:
- org.jacoco:org.jacoco.ant [0.8.14 -> 0.8.15]
declared in root project
One declared version can also have different latest versions across the projects, which happens when a platform bounds the module in some of them and not in others. Each of those latest versions is shown on an entry of its own, with the projects included the same way, rather than the newest of them being shown for every project:
The following dependencies are using the latest milestone version:
- com.google.inject:guice:2.0
constrained by the platform :platform in root project
The following dependencies have later milestone versions:
- com.google.inject:guice [2.0 -> 7.0.0]
declared in :platform
So a coordinate's group and name can appear on two entries, and in two sections of one report; the projects printed on each are what distinguish them.
The plain text and HTML reports print the first five projects and a count of the
rest (declared in :app, :lib, ... and 60 others). The JSON and XML reports
always include the complete list, so use one of them when a tool needs every
project.
A project is printed as its path in the build tree, so a project of an included
build reads as :child:app and that build's root as :child. In each build the
root project's own path is :, which would otherwise put the projects of two
builds under one name.
With the settings plugin applied (see Applying the
plugin), the dependencyUpdates task is registered in
the root project and every other project contributes to it. No project applies
a plugin itself, and a root build script is not required—add one only if you
want to configure the task. The report also covers the plugins the settings
script declares, which appear in no project's buildscript; a version pinned in
pluginManagement for a plugin the build never applies is not reported.
The settings plugin registers the root project's dependencyUpdates task
before the root build script runs, so a build script that registers its own
task by that name now fails with a duplicate-task error—rename yours.
The settings that control resolution (revision, rejectVersionIf or a full
resolutionStrategy, filterConfigurations, filterDeclaredConfigurations,
checkConstraints, checkBuildEnvironmentConstraints, and
rejectOutOfBoundVersions) are inherited from
the nearest project up the hierarchy whose task set them. Configuring the root
project's task therefore covers every project, unless a subproject configures
its own (see Task properties).
An included build is a separate build with its own settings script, so its
projects are not part of this build's report. Apply the settings plugin in the
included build's settings script as well, and run its dependencyUpdates task
separately. buildSrc is a separate build too, and is likewise excluded. This
is not specific to the settings plugin—an included build has never been covered
by default.
To report on every build in one invocation, register a lifecycle task that depends on each included build's task. Each build writes its own report, which suits builds that are developed independently, as each keeps its own settings:
"build.gradle.kts":
tasks.register("allDependencyUpdates") {
gradle.includedBuilds.forEach { dependsOn(it.task(":dependencyUpdates")) }
}
"build.gradle":
tasks.register("allDependencyUpdates") {
gradle.includedBuilds.each { dependsOn(it.task(':dependencyUpdates')) }
}
Every included build needs the plugin applied for its dependencyUpdates task
to exist. A build that must stay unmodified can have the plugin injected by an
init script instead.
An included build's project can instead be merged into this build's report, by
declaring it in the dependencyUpdatesAggregation configuration of the project
that aggregates. Declare it by the coordinates that the include substitutes, and
apply the plugin in the included build, so that a partial result exists to
merge. Each declaration merges the one project it resolves to. A project of this
build that the aggregating project's own tree does not cover, such as a sibling,
is declared the same way:
"build.gradle.kts":
dependencies {
dependencyUpdatesAggregation("com.example:child:1.0")
}
"build.gradle":
dependencies {
dependencyUpdatesAggregation 'com.example:child:1.0'
}
A project can have a dependencyUpdates task of its own, reporting on itself
and its subprojects (see Other ways to apply the
plugin). Run it by its path to get just that
report:
./gradlew :subproject:dependencyUpdates
Under isolated projects a project plugin cannot register a task in another project, so a project only contributes to the aggregate report if it applies a plugin itself. The settings plugin covers this: it applies a plugin to each project as the project is evaluated, so the recommended setup works unchanged (see Applying the plugin).
A build that cannot apply the settings plugin can still cover every project (see Contributor plugin).
Under isolated projects, contributing projects that share a group and name are aggregated as one, and a warning is printed to the console for any project missing from the report.
The settings plugin is the recommended way to apply the plugin (see Applying the plugin). The options below cover builds that need something different:
plugins
block and legacy plugin application);In the snippets below, replace $version with the current release, shown in the
badge at the top of this page.
[!IMPORTANT] When the settings plugin is also applied, request the per-project plugin without a version—the settings plugin already puts it on every project's classpath, and a versioned request fails to resolve. This includes a version catalog alias, which always includes a version.
plugins block"build.gradle.kts":
plugins {
id("io.github.ben-manes.versions") version "$version"
}
"build.gradle":
plugins {
id "io.github.ben-manes.versions" version "$version"
}
[!TIP] Prefer the
pluginsblock—it is the modern replacement forbuildscript-based plugin application.
"build.gradle.kts":
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath("io.github.ben-manes:gradle-versions-plugin:$version")
}
}
apply(plugin = "io.github.ben-manes.versions")
"build.gradle":
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath "io.github.ben-manes:gradle-versions-plugin:$version"
}
}
apply plugin: "io.github.ben-manes.versions"
A build that cannot apply the settings plugin—under isolated projects, where a
project plugin cannot register a task in another project (see Isolated
projects)—can keep applying io.github.ben-manes.versions
in the root project, and apply io.github.ben-manes.versions.contributor in
every other project, typically from a convention plugin they already share:
"buildSrc/src/main/kotlin/my-conventions.gradle.kts":
plugins {
id("io.github.ben-manes.versions.contributor")
}
"buildSrc/src/main/groovy/my-conventions.gradle":
plugins {
id 'io.github.ben-manes.versions.contributor'
}
The convention plugin's own build must have the plugin on its classpath, e.g. as
an implementation("io.github.ben-manes:gradle-versions-plugin:$version")
dependency in buildSrc/build.gradle.kts.
The contributor plugin registers only the producer that feeds the aggregate
report, so dependencyUpdates remains a single task in the root project. The
main plugin is a superset of the contributor plugin: a project that applies
io.github.ben-manes.versions instead still feeds the aggregate report, and
also has a dependencyUpdates task of its own, covering itself and its
subprojects.
You can also transparently add the plugin to every Gradle project that you run
via a
Gradle init script.
Apply the settings plugin from beforeSettings, which covers every project of
the build and works under isolated projects (see Isolated
projects). A dependencyUpdates task is registered in
every build that runs, so an included build is reported without being modified
(see Composite builds):
"$HOME/.gradle/init.d/add-versions-plugin.init.gradle.kts":
import com.github.benmanes.gradle.versions.VersionsSettingsPlugin
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
initscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath("io.github.ben-manes:gradle-versions-plugin:+")
}
}
gradle.beforeSettings(Action<Settings> {
pluginManager.apply(VersionsSettingsPlugin::class.java)
})
gradle.rootProject(Action<Project> {
tasks.withType(DependencyUpdatesTask::class.java).configureEach {
// configure the task, for example wrt. resolution strategies
}
})
"$HOME/.gradle/init.d/add-versions-plugin.gradle":
import com.github.benmanes.gradle.versions.VersionsSettingsPlugin
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
initscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath 'io.github.ben-manes:gradle-versions-plugin:+'
}
}
beforeSettings { settings ->
settings.pluginManager.apply(VersionsSettingsPlugin)
}
gradle.rootProject {
tasks.withType(DependencyUpdatesTask).configureEach {
// configure the task, for example wrt. resolution strategies
}
}
A script has no implicit import for the plugin's types, so the imports at the top of these snippets are required to reference them by their simple names.
An init script resolves the plugin on a classpath of its own, so a build that
applies the plugin itself ends up with a second copy of it. An init script runs
before the build's own scripts, so its copy is the one that registers the task
and the build's copy does nothing, which leaves such a build working as it did.
For the same reason the plugin is absent from the project's own classpath, so a
plugins block that requests it alongside an init script needs a version,
unlike one in a build whose settings script applies the settings plugin (see
Other ways to apply the plugin).
Have a look at
examples/kotlin
and
examples/groovy
# Publish the latest version of the plugin to mavenLocal()
$ ./gradlew publishToMavenLocal
# Try out the samples
$ ./gradlew -p examples/kotlin dependencyUpdates
$ ./gradlew -p examples/groovy dependencyUpdates
The plugin requires Gradle 8.4 or later, checked when the plugin is applied. It targets Java 8 bytecode, so it runs on any JVM that can run Gradle itself. Parallel execution, the configuration cache, configure on demand, and isolated projects (see Isolated projects) are supported.
To migrate to the current version, start at the section for the version your build is on and work upward. Each section migrates to the version covered by the section above it, and the topmost migrates to the current release. Importants are must-dos, Tips are actions you should or may want to take, and Notes are things worth knowing that need no action.
In the next release, the report is held to the bounds written in the build without a rule written for it, and a coordinate with one declared version and different latest versions across the aggregated projects is shown on one entry per latest version, where the entries were merged into the newest of them before:
[!IMPORTANT]
- A candidate outside a
strictlyorrejectbound written in the build, outside a dynamic version declared on the buildscript classpath, or outside the version fixed by a consumed platform, is left out of the report, where it was listed before. SetrejectOutOfBoundVersions = falseto list it again, or pass--no-reject-out-of-bound-versionsfor a single run (see Respecting declared bounds).- A coordinate's group and name can now appear on two entries of one report, and in two of its sections. The projects for each entry are included in
projects(see Multi-project builds), which is what distinguishes them. A tool that keys the entries by group and name alone has to key them by the projects as well.
[!TIP]
- Drop
!satisfiesDeclaredBoundfrom arejectVersionIfrule, since the bound is now applied byrejectOutOfBoundVersions. The member is deprecated and will be removed in a later release; a warning is printed once per project when a rule reads it, and a Kotlin DSL build that treats compiler warnings as errors has to drop the clause before upgrading. With the clause still in a rule, the same candidates are rejected under--no-reject-out-of-bound-versionsas without it.
[!NOTE]
- The presence of a version catalog no longer changes whether a plugin versioned inline as a range in the
pluginsblock is offered an upgrade past that range. The upgrade was withheld when an unused alias for the same plugin was present in the catalog and offered when it was not (see Respecting declared bounds).- A module that a platform bounds in one project and not in another is now up to date in the bounded project and outdated in the other, rather than outdated in both. The merged entry printed an upgrade that is not available in the bounded project.
- A split entry can include an attribution line that the merged one left out. The line is left out when a project outside the platform's importers declares the module. Once the entries are split, that project is on an entry of its own, so the line is printed again (see Report format).
In v0.61.0, a project is printed as its path in the build tree wherever one
appears in the report, so the projects of an included build no longer share the
: that stands for each build's own root. The platforms a build imports through
its own platform projects are reported, as are the configurations left
uninspected by a resolutionStrategy that throws:
[!IMPORTANT]
- A project of an included build is printed as its build tree path, so an entry reads
declared in :childwhere it readdeclared in root project(see Multi-project builds). The same paths appear in the JSON and XML reports, inprojects.- A
buildSrcbuild is printed the same way when its task is run from the outer build, as:buildSrc:dependencyUpdates. Its report is headed:buildSrcrather than:, and its projects are printed beneath that path. Running the task from insidebuildSrcmakes it the root of its own build tree, which still reads:.- An attribution line reading
imported by the platform :platformscan now be printed under an entry, showing the platform projects the build imports the dependency through. A tool that parses the plain text report line by line has to skip it, as it already does for the other attribution lines (see Report format). The same paths appear in the JSON and XML reports, inplatformProjects.- The plain text report now has a section listing the configurations that could not be inspected, after the dependency ones, where they were logged at info level and left out silently before. They appear in the JSON and XML reports under
skipped, whichcountdoes not include, so a tool totalling the report has to leave it out too (see Report output).- A custom
outputFormatterthat callscopyonDependencyOutdated,DependencyLatestorDependencyUnresolvedhas to be recompiled against this release. Each gained aplatformProjectsproperty, and a data class has exactly onecopy, so the one earlier releases shipped is gone. Their constructors are unchanged: a formatter that only reads the report, as the documented ones do, needs nothing.
[!TIP] An entry can show a configuration name that
filterConfigurationscannot match, such as a declarable configuration read through a resolvable classpath that extends it. Rejecting the classpath instead removes the build's own dependencies with it.filterDeclaredConfigurationsrejects the entry by the name it shows, and leaves what the task checks alone.
[!NOTE]
- A dependency that two builds of a composite declare at different versions is now reported as divergent. The attribution showing the projects for each version was left out while every entry had the same project path, which in a composite was always
:.- With
checkConstraints, an entry is now printed for each platform the build's own platform projects import. The modules those constraints bound were reported as up to date, with nothing to show the coordinate to bump (see Constraints).- A configuration that declares a platform is resolved a second time, to find those imports, so a
beforeResolvehook on it runs once more than it did. A configuration declaring no platform resolves nothing extra.
In v0.60.0, the configuration a dependency was declared directly against is printed, so a plugin that fills a classpath of its own when it is applied no longer reads as the build declaring the dependency. The reason a dependency constraint was declared with is printed too, and a component selection rule can keep the report inside the bound the build declared:
[!IMPORTANT]
- An attribution line can now be printed under an entry that had none, showing the configuration the dependency was declared against. A tool that parses the plain text report line by line has to skip it, as it already does for the other attribution lines (see Report format). The same names appear in the JSON and XML reports, in
configurations, which until now contained only what a plugin contributed, so a tool reading that field has to readcontributedalongside it to tell the two apart.- The reason on a dependency constraint declared with
becauseis now printed, where only a dependency's reason appeared before. It prints on the same line as a dependency's reason does, so a tool that already handles one handles both.
[!TIP]
- A component selection rule can keep the report inside what the build declared, with
rejectVersionIf { !satisfiesDeclaredBound }. A module bounded by astrictlyor by a platform the build consumes is then bounded there, so only versions the build can actually take are offered (see Respecting declared bounds).- A Kotlin DSL build script that worked around the Gradle 9 overload ambiguity by spelling out
Action<ComponentSelectionWithCurrent>can go back to the untypedall { }andwithModule(id) { }forms.
In v0.59.0, the version another resolution found for a dependency that failed
to resolve is reported, and the partial result of every project is collected
under the project that aggregates them, so no build directory is created in a
project that exists only for a nested include:
[!IMPORTANT]
- A declared version that resolved in one place and failed in another is now reported twice: once with the version found for it, and again as unresolved. A dependency declared without a version doubles the same way, as undeclared and unresolved. The JSON and XML reports count it in each place. A tool reading
countas a dependency total, or treating the sections as disjoint, has to allow for the overlap (see Report format).- The unresolved section of the plain text report now includes the declared version, as every other section does. A tool that parses that section line by line has to allow it.
[!TIP] Run
./gradlew dependencyUpdates --clean-legacy-partialsonce to remove thebuild/dependencyUpdates/partial.jsonthat earlier releases wrote into each project. Thecleantask removes it from a project that directly or indirectly applies the Base plugin, but a project with no build script has nocleantask.
In v0.58.0, the reporters print through the build's logger, and attribution lines are added to the reports:
[!IMPORTANT]
- The console summary now prints at the lifecycle log level, so
--quietsuppresses it. The report file is still written; read it, or drop--quiet, if a script was piping the console output (see Report format).- An indented attribution line may be printed under an entry, showing the projects that declared a divergent version (see Multi-project builds) or the configuration a plugin contributed it into (see The
dependencyUpdatestask). A tool that parses the plain text report line by line has to skip them; the same information appears as fields in the JSON and XML reports.
In v0.57.0 the settings plugin can be applied from an init script:
[!IMPORTANT] An init script that applies
VersionsPlugintoallprojectsreports per project rather than once, omits the plugins that the settings script declares, and fails under isolated projects. Apply the settings plugin frombeforeSettingsinstead (see Initialization script).
In v0.56.0 the settings plugin is added, and it becomes the recommended setup:
[!TIP] Apply
io.github.ben-manes.versions.settingsin the settings script (see Applying the plugin) and removeio.github.ben-manes.versionsfrom your build scripts. A project that keeps the main plugin for a separate per-project report must request it without a version, because the settings plugin already puts the plugin on every project's classpath.
[!NOTE]
- A build that applied
io.github.ben-manes.versions.contributorfrom a convention plugin for isolated projects support no longer needs it: the settings plugin covers every project. The contributor plugin remains available for builds that cannot apply a settings plugin.- Task configuration is unchanged: configure
dependencyUpdatesin the root build script as before.
In v0.55.0 the plugin moves from the com.github.ben-manes namespace to
io.github.ben-manes, and the minimum supported Gradle version rises to 8.4:
[!IMPORTANT]
- Move a
buildscriptorinitscriptclasspathdependency to theio.github.ben-manes:gradle-versions-plugincoordinate. v0.54.0 is the last release published undercom.github.ben-manes:gradle-versions-plugin, so no further updates are published under the old coordinate.- Under the configuration cache, a custom
outputFormattercannot reach the project or the build script from inside the closure, because it runs at execution time (see Report format). Read what it needs into local variables inside theconfigureblock, and use thePlainTextReporterconstructor that takes the project path, as shown below.Kotlin
"build.gradle.kts":
import com.github.benmanes.gradle.versions.reporter.PlainTextReporter import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask tasks.named<DependencyUpdatesTask>("dependencyUpdates").configure { val projectPath = project.path outputFormatter { PlainTextReporter(projectPath, revision, gradleReleaseChannel).write(System.out, this) } }Groovy
"build.gradle":
import com.github.benmanes.gradle.versions.reporter.PlainTextReporter tasks.named("dependencyUpdates").configure { def projectPath = project.path def taskRevision = revision def releaseChannel = gradleReleaseChannel outputFormatter { result -> new PlainTextReporter(projectPath, taskRevision, releaseChannel).write(System.out, result) } }Groovy also needs
revisionandgradleReleaseChannelread up front, because the closure is coerced to anActionwithout a delegate. In a precompiled script plugin a top-levelvalis a field of the script, so hoisting the value out of theconfigureblock does not work.
[!TIP] Switch the plugin ID from
com.github.ben-manes.versionstoio.github.ben-manes.versions. The legacy ID is deprecated but keeps receiving releases, so this can happen at your convenience; only the main plugin has a legacy ID.
[!NOTE] v0.55.0 also reworks how the merged report of a multi-project build is produced: it is aggregated from a task in each project, which adds support for parallel execution, the configuration cache, and isolated projects (see Multi-project builds). The report content is unchanged, as is task configuration apart from a custom
outputFormatterunder the configuration cache.
Then continue with the v0.55.0 steps above.
This plugin only reports. To apply the updates it finds automatically:
libs.versions.toml) based on
this plugin's reportOther related tools:
(top 30 of 79)
Groovy
66.3%
Kotlin
33.6%
This plugin reports which of your build's dependencies, plugins, and Gradle itself have newer versions available, in the spirit of the Maven Versions Plugin.
Table of contents
dependencyUpdates task
The recommended way to add the Gradle Versions Plugin to any build is to apply the settings plugin once in the settings script. This approach allows the plugin to report updates for the plugins and buildscript dependencies that the settings script declares, in addition to each project's own plugins, buildscript dependencies, and dependencies. It also automatically covers every subproject in a multi-project build (see Multi-project builds).
"settings.gradle.kts":
plugins {
id("io.github.ben-manes.versions.settings") version "$version"
}
"settings.gradle":
plugins {
id 'io.github.ben-manes.versions.settings' version '$version'
}
[!IMPORTANT] Replace
$versionwith the current release, shown in the badge at the top of this page.
After adding the settings plugin to your build, run the dependencyUpdates task
to get the report of up-to-date and outdated dependencies (see The
dependencyUpdates task):
./gradlew dependencyUpdates
The report prints to the console and is written to
build/dependencyUpdates/report.txt:
------------------------------------------------------------
: Project Dependency Updates (report to plain text file)
------------------------------------------------------------
The following dependencies have later milestone versions:
- com.google.inject:guice [2.0 -> 7.0.0]
https://github.com/google/guice
- org.springframework.boot:spring-boot-dependencies [1.5.8.RELEASE -> 4.1.0]
https://spring.io/projects/spring-boot
Gradle release-candidate updates:
- Gradle: [8.4 -> 9.7.0]
The task is configured in the root project's build script:
"build.gradle.kts":
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
revision = "release"
outputFormatter = "json"
}
"build.gradle":
tasks.named("dependencyUpdates").configure {
revision = 'release'
outputFormatter = 'json'
}
A build with no root build script can configure the task from the settings script instead:
"settings.gradle.kts":
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
gradle.rootProject {
tasks.withType(DependencyUpdatesTask::class.java).configureEach {
revision = "release"
outputFormatter = "json"
}
}
"settings.gradle":
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
gradle.rootProject {
tasks.withType(DependencyUpdatesTask).configureEach {
revision = 'release'
outputFormatter = 'json'
}
}
Every task property is covered in Task properties, which opens with a recommended configuration most builds need: a stability filter so that a pre-release version is not offered as an update, and a bound so that an upgrade the build has ruled out is not offered either.
dependencyUpdates taskDisplays a report of the project dependencies that are up-to-date, exceed the
latest version found, have upgrades, or failed to be resolved. When a dependency
cannot be resolved the exception is logged at the info level.
The report includes dependencies declared through a version catalog, but the plugin only reports—it never edits build files or the catalog. See Related plugins for tools that apply updates automatically.
The report also includes the dependencies that a plugin contributes lazily
rather than the build declaring them, such as the Kotlin standard library and
the tool versions of the jacoco, checkstyle, and pmd plugins. Their
current version is whatever the contributing plugin supplies when the task
runs, so a tool version the build never sets is reported at the default
bundled with Gradle—a version that appears nowhere in the build script. An
extra line is printed under such an entry, so it does not read as a resolution
bug. The line shows the configuration the plugin declared the dependency
against:
- org.jacoco:org.jacoco.ant [0.8.11 -> 0.8.13]
contributed by a plugin into the 'jacocoAnt' configuration
Set the extension's version, such as jacoco.toolVersion, to control what the
report compares against.
A plugin that fills its classpath when it is applied, rather than when the configuration is first resolved, cannot be told apart from the build declaring the dependency. Such an entry shows the configuration the dependency was declared against instead:
- org.jetbrains.kotlin:kotlin-build-tools-impl [2.4.0 -> 2.4.10]
declared in the 'kotlinAbiValidationCompatClasspath' configuration
A configuration the build declares against directly, such as a tool
configuration of its own, is shown the same way. Reject the name an entry
shows with filterDeclaredConfigurations to
leave it out of the report.
Gradle updates are checked for on the current, release-candidate and
nightly release channels. The plain-text report displays Gradle updates as a
separate category in breadcrumb style, excluding nightly builds. The XML and
JSON reports cover all three release channels: whether a release is an update
with respect to the Gradle instance running the build, whether an update check
failed, and a reason field explaining failures or missing information. The
update check may be disabled using the checkForGradleUpdate flag.
To find the latest version of a dependency, the task queries each repository
for the versions available there. Gradle caches the result for 24 hours, so a
version published within the last day may be missing from the report, because
the cached answer predates it. Re-run with --refresh-dependencies to bypass
the cache and query the repositories again:
./gradlew dependencyUpdates --refresh-dependencies
[!TIP] The
--refresh-dependenciesflag applies to the whole build rather than to this task alone, so it also re-checks every other dependency the build resolves and is slower than a normal run. Use it when a release you are expecting does not appear, not routinely.
The properties below each solve a different problem, and most builds end up wanting the same few. This is a complete starting point, assembled from the sections that follow:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
fun String.isNonStable(): Boolean {
val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { uppercase().contains(it) }
val regex = "^[0-9,.v-]+(-r|-jre|-android)?$".toRegex()
val isStable = stableKeyword || regex.matches(this)
return isStable.not()
}
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
checkConstraints = true
rejectVersionIf {
candidate.version.isNonStable() && !currentVersion.isNonStable()
}
}
def isNonStable = { String version ->
def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { it -> version.toUpperCase().contains(it) }
def regex = /^[0-9,.v-]+(-r|-jre|-android)?$/
return !stableKeyword && !(version ==~ regex)
}
tasks.named("dependencyUpdates").configure {
checkConstraints = true
rejectVersionIf {
isNonStable(candidate.version) && !isNonStable(currentVersion)
}
}
checkConstraints adds the versions a constraints block manages to the
report (see Constraints).strictly or reject bound written in the build,
outside a dynamic version declared on the buildscript classpath, or outside
the version fixed by a consumed platform, is already left out by
rejectOutOfBoundVersions, which is on by default (see Respecting declared
bounds).Each piece stands alone: drop any line whose behavior you do not want, and the rest keep working.
The configuration filters are absent only because their arguments are build-specific: the names to reject come from your own report.
In Kotlin the isNonStable extension replaces a helper of that name already in
the build. A top-level fun isNonStable(version: String) compiles to the same
JVM signature, so keeping both fails the build with a platform declaration
clash.
Each property below configures the report or where it is written, and links to the section that describes it. A property taking a predicate or a closure has no command line option, since no command line can express the logic.
| Property | Values | Default | Option |
|---|---|---|---|
revision | release, milestone, integration | milestone | --revision |
gradleReleaseChannel | current, release-candidate, nightly | release-candidate | --gradle-release-channel |
checkForGradleUpdate | true, false | true | --[no-]check-for-gradle-update |
checkConstraints | true, false | false | --[no-]check-constraints |
checkBuildEnvironmentConstraints | true, false | false | --[no-]check-build-environment-constraints |
filterConfigurations | a Spec<Configuration> | every configuration | |
filterDeclaredConfigurations | a Spec<String> | every name | |
rejectOutOfBoundVersions | true, false | true | --[no-]reject-out-of-bound-versions |
rejectVersionIf | a predicate over the candidate | nothing rejected | |
outputFormatter | text, json, xml, html, a comma separated list of those, or a Reporter | text | --output-formatter |
outputDir | a directory path | <buildDirectory>/dependencyUpdates | --output-dir |
reportfileName | a file name, without the extension | report | --report-file-name |
gradleVersionsApiBaseUrl | a URL | https://services.gradle.org/versions/ | --gradle-versions-api-base-url |
cleanLegacyPartials | true, false | false | --[no-]clean-legacy-partials |
The resolutionStrategy block is configured
rather than set to a value, so it is absent from the table above.
Each option in the table above changes a property for a single run, without an edit to the build script. Use one to see what a check leaves out, then return to the configured behavior on the next run:
./gradlew dependencyUpdates --no-check-constraints
--[no-] marks the options that take no argument, so
--no-check-for-gradle-update turns off a check that is enabled in the build
script. Every option, its description, and the values --revision and
--gradle-release-channel accept are printed by gradle help --task dependencyUpdates.
Where more than one is set, the command line option is the one that applies, ahead of a system property and of what is configured in the build.
An option applies within the build it is invoked in. Where a report merges an included build, that build's rows are resolved with its own configuration, since the option is not passed to the included build. A system property is, being set for the whole JVM.
Two properties leave things out of the report. filterConfigurations
controls which configurations the task checks: a rejected configuration is
never resolved, and nothing reachable only through it is reported.
filterDeclaredConfigurations controls which entries stay in the report once
the checks have run, matched against the configuration name printed on the
entry itself.
filterConfigurationsThe task checks every resolvable configuration of every project. A build that only acts on its main classpaths can restrict the check to them:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
filterConfigurations = Spec<Configuration> {
it.name == "runtimeClasspath" || it.name == "compileClasspath"
}
}
tasks.named("dependencyUpdates").configure {
filterConfigurations {
it.name == "runtimeClasspath" || it.name == "compileClasspath"
}
}
A dependency is left out of the report once every configuration that reaches
it is rejected, and so is everything reachable only through a rejected
configuration—rejecting compileClasspath removes the build's own
dependencies too. Reach for this filter when a whole configuration is noise:
a skipped configuration also costs no version lookups, which suits the
classpaths a plugin fills for its own tooling, at versions the build never
chose. The Kotlin Gradle Plugin and
Android Gradle Plugin sections below give ready
sets.
filterDeclaredConfigurationsStart from the report line. Rejecting the name an entry shows removes the entry:
- org.jacoco:org.jacoco.ant [0.8.11 -> 0.8.13]
contributed by a plugin into the 'jacocoAnt' configuration
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
filterDeclaredConfigurations = Spec<String> { it != "jacocoAnt" }
}
tasks.named("dependencyUpdates").configure {
filterDeclaredConfigurations { it != "jacocoAnt" }
}
It matches exactly the name printed on the entry, from a "contributed by a
plugin into" line or a "declared in" one—a tool configuration the build
declares against directly is rejectable the same way. An entry is left out of
the report when every name it shows is rejected, and an entry printed with no
configuration name is never affected: dependencies declared through
implementation and its siblings are printed without one, so no rejection
applies to them, and where such a declaration also backs a named entry, the
rejection removes the attribution line rather than the dependency.
Both filters silence a tooling configuration like the KGP and AGP sets
below; prefer filterConfigurations there, since it also skips the lookups.
The two differ when the name an entry shows is not one the task checks (see
The dependencyUpdates task): a declarable
configuration read through a resolvable classpath that extends it, and
implementation with a plugin's contribution in it, both show a name that
filterConfigurations cannot match. filterDeclaredConfigurations matches
the name shown, with no side effect on what is checked. Buildscript and
settings classpath entries are ordinarily printed with no configuration name,
so neither property affects them; an entry a plugin contributes shows
classpath and can be rejected like any other entry.
The Kotlin Gradle Plugin fills four fixed classpaths for its own tooling,
plus one kotlinCompilerPluginClasspath<SourceSet> per source set—a JVM
test suite
adds one too—so a list of names goes stale as the build grows. Match the
family by prefix; at KGP 2.4.10:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
fun isKgpInternal(configurationName: String): Boolean {
val kgpInternalConfigurations = setOf(
"kotlinCompilerClasspath",
"kotlinBuildToolsApiClasspath",
"kotlinAbiValidationCompatClasspath",
"kotlinKlibCommonizerClasspath",
)
return configurationName in kgpInternalConfigurations ||
(configurationName.startsWith("kotlinCompilerPluginClasspath") &&
configurationName != "kotlinCompilerPluginClasspath")
}
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
filterConfigurations = Spec<Configuration> { !isKgpInternal(it.name) }
}
def isKgpInternal = { String configurationName ->
def kgpInternalConfigurations = [
"kotlinCompilerClasspath",
"kotlinBuildToolsApiClasspath",
"kotlinAbiValidationCompatClasspath",
"kotlinKlibCommonizerClasspath",
]
return kgpInternalConfigurations.contains(configurationName) ||
(configurationName.startsWith("kotlinCompilerPluginClasspath") &&
configurationName != "kotlinCompilerPluginClasspath")
}
tasks.named("dependencyUpdates").configure {
filterConfigurations { !isKgpInternal(it.name) }
}
The prefix stops short of the unsuffixed kotlinCompilerPluginClasspath,
which is still checked, and stays narrower than dropping everything that
starts with kotlin. A compiler plugin the build itself declares resolves
through these same suffixed classpaths and is filtered with them. Its Gradle
plugin's marker is still reported, so a version shared between the two stays
visible, but a compiler plugin versioned apart from its Gradle plugin is left
out with nothing in its place—keep the classpath it resolves through if you
declare one.
The Android Gradle Plugin fills a set of its own. At AGP 9.3.1, which builds
in its Kotlin support rather than applying a separate Kotlin plugin, that is
androidLintTool, two of the Kotlin classpaths above, and a
unified-test-platform-* family thirteen configurations wide. Match that
family by prefix as well: which configurations it contains changes between AGP
releases, and no configuration a build fills itself starts with that prefix.
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
val agpInternal = setOf(
"androidLintTool",
"kotlinBuildToolsApiClasspath",
"kotlinCompilerClasspath",
)
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
filterConfigurations = Spec<Configuration> {
it.name !in agpInternal && !it.name.startsWith("unified-test-platform-")
}
}
def agpInternal = [
"androidLintTool",
"kotlinBuildToolsApiClasspath",
"kotlinCompilerClasspath",
]
tasks.named("dependencyUpdates").configure {
filterConfigurations {
!agpInternal.contains(it.name) &&
!it.name.startsWith("unified-test-platform-")
}
}
These names come from the plugins at the versions above, not from Gradle, and they change between plugin releases. Take the set from your own report rather than from this page, and keep the matches exact wherever a plugin's configurations share a prefix with ones the build fills itself.
If you
manage
transitive dependency versions with a constraints block, you can enable
checking of constraints by specifying the checkConstraints attribute of the
dependencyUpdates task. If you want to check external constraints (defined in
init scripts or by Gradle itself) you can do so by specifying the
checkBuildEnvironmentConstraints attribute of the dependencyUpdates task.
The attribute covers the constraints a project declares, on its own
configurations or on ones they extend. A project applying the
java-platform
plugin to define a BOM declares its constraints that way, so running the task on
the platform project reports them.
A project that consumes a platform does not declare that platform's
constraints, under any of platform("group:artifact:version"),
enforcedPlatform, or platform(project(":platform")). Gradle supplies those
versions to the consumer as resolution metadata, which checkConstraints does
not read, so they are not enumerated in the consumer's report. This does not
affect the modules the consumer declares itself: a versionless declaration whose
version the platform supplies is reported and offered updates as usual. Only a
module the consumer never declares is absent, and those appear in the platform
project's own report.
The platform a platform project imports is reported here, though. A build whose
platform project declares api(platform("group:artifact:version")) reaches that
BOM's constraints, but the BOM itself is declared nowhere the report covers, and
a platform in an included build is further out of reach, since that build is
reported separately (see Composite builds). With
checkConstraints an entry for the BOM is printed beside the versionless
declarations whose versions it supplies, so the coordinate to bump appears in
the report. Where the build declares a version for every module, the walk goes
no further than the platform project itself. It also stops at the first
published platform: a BOM imported by that BOM reflects the BOM's version rather
than the build's, and a BOM that arrives as a library's resolution metadata was
never imported by the build, so neither is reported. Every platform project
that imports the BOM is printed on the BOM's entry, by build tree path.
The platform behind a constrained module's version is included in that module's
own entry either way, as constrained by the platform :platform for a platform
project included in the build and constrained by the platform group:artifact
for a BOM, whose own version appears on its own entry. The platform is only
printed when its constraint is the same version the entry shows. If something
else in the build required a higher version and won out, changing the platform
would not change that version, so nothing is printed. The coordinate to bump
appears on the entry where the version is stated (see
Respecting declared bounds). This line does not
depend on checkConstraints. The same names are present in the JSON and XML
reports as constrainedBy, beside the platformProjects importers of a
platform's own entry.
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
checkConstraints = true
checkBuildEnvironmentConstraints = true
}
tasks.named("dependencyUpdates").configure {
checkConstraints = true
checkBuildEnvironmentConstraints = true
}
The revision task property controls the Ivy resolution
strategy
for determining what constitutes the latest version of a dependency. Maven's
dependency metadata does not distinguish between milestone and release versions.
The following strategies are natively supported by Gradle:
The strategy can be specified either on the task or as a system property for ad hoc usage:
./gradlew dependencyUpdates -Drevision=release
Because Maven repositories do not mark pre-release versions, an alpha or release candidate can still appear as the latest version under any revision. To only be offered stable updates, reject pre-release candidates (see Filtering unstable versions).
To further control which versions are accepted, define what counts as an unstable version. There is no agreed standard, but this is a good starting point:
fun String.isNonStable(): Boolean {
val stableKeyword = listOf("RELEASE", "FINAL", "GA").any { uppercase().contains(it) }
val regex = "^[0-9,.v-]+(-r|-jre|-android)?$".toRegex()
val isStable = stableKeyword || regex.matches(this)
return isStable.not()
}
def isNonStable = { String version ->
def stableKeyword = ['RELEASE', 'FINAL', 'GA'].any { it -> version.toUpperCase().contains(it) }
def regex = /^[0-9,.v-]+(-r|-jre|-android)?$/
return !stableKeyword && !(version ==~ regex)
}
The trailing -jre and -android keep Guava's ordinary releases out of the
unstable set. A version that spells its qualifier some other way, such as
13.4.0.jre11, still matches as unstable and needs the pattern extended, so
check this against the versions in your own build before relying on it.
You can then configure Component Selection
Rules.
The current version of a component can be retrieved with the currentVersion
property. You can either use the simplified syntax rejectVersionIf { ... } or
configure a complete resolution strategy. Multiple registrations compose, so a
candidate is rejected if any registered filter rejects it.
Example 1: reject all non-stable versions
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
rejectVersionIf {
candidate.version.isNonStable()
}
}
Example 2: disallow release candidates as upgradable versions from stable versions
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
rejectVersionIf {
candidate.version.isNonStable() && !currentVersion.isNonStable()
}
}
Example 3: using the full syntax
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
resolutionStrategy {
componentSelection {
all {
if (candidate.version.isNonStable() && !currentVersion.isNonStable()) {
reject("Release candidate")
}
}
}
}
}
Example 4: disallow candidates less mature than the current version, so an -rc
keeps being offered -rc updates but never a -beta
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
val qualifiers = listOf("preview", "alpha", "beta", "m", "cr", "rc") // order is important
fun maturityLevel(version: String): Int {
val index = qualifiers.indexOfFirst {
version.matches(".*[.\\-]$it[.\\-\\d]*".toRegex(RegexOption.IGNORE_CASE))
}
return if (index < 0) qualifiers.size else index
}
rejectVersionIf {
maturityLevel(candidate.version) < maturityLevel(currentVersion)
}
}
Example 1: reject all non-stable versions
tasks.named("dependencyUpdates").configure {
rejectVersionIf {
isNonStable(candidate.version)
}
}
Example 2: disallow release candidates as upgradable versions from stable versions
tasks.named("dependencyUpdates").configure {
rejectVersionIf {
isNonStable(candidate.version) && !isNonStable(currentVersion)
}
}
Example 3: using the full syntax
tasks.named("dependencyUpdates").configure {
resolutionStrategy {
componentSelection {
all {
if (isNonStable(candidate.version) && !isNonStable(currentVersion)) {
reject('Release candidate')
}
}
}
}
}
Example 4: disallow candidates less mature than the current version, so an -rc
keeps being offered -rc updates but never a -beta
tasks.named("dependencyUpdates").configure {
def qualifiers = ['preview', 'alpha', 'beta', 'm', 'cr', 'rc'] // order is important
def maturityLevel = { String version ->
def index = qualifiers.findIndexOf { version ==~ /(?i).*[.\-]$it[.\-\d]*/ }
return (index < 0) ? qualifiers.size() : index
}
rejectVersionIf {
maturityLevel(candidate.version) < maturityLevel(currentVersion)
}
}
The report is held to the bounds written in the build. A candidate outside a
bound on the declaration, or outside the version fixed by a consumed platform,
is left out, so the only versions listed are ones the build can actually be
moved to. Set rejectOutOfBoundVersions to false to turn that off:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
rejectOutOfBoundVersions = false
}
tasks.named("dependencyUpdates").configure {
rejectOutOfBoundVersions = false
}
To see what is left out, without an edit to the build script, run
./gradlew dependencyUpdates --no-reject-out-of-bound-versions. Run it to
learn what a bounded module could be moved to if the bound were lifted, such
as whether a fix was released for the module ahead of its platform.
A bound is read the way dependency resolution reads it, so strictly "[5.3, 6[" admits 5.3.26 and excludes 6.0.1, and reject "[3.0,)" excludes
everything from 3.0 up. Only strictly and reject bound a candidate: a
require version is a floor resolution may rise above, a range included, and a
prefer version only breaks a tie, so a plain
implementation("group:name:1.2.3") is not bounded.
The bound is not applied in two cases where it makes no difference. A candidate no newer than the version in use is not an upgrade, so it stays, and a module at a version above everything published is still listed on the exceeded row. Where the version in use already lies outside the bound, the bound is not applied at all: once a transitive requirement has pushed the version past a platform, the platform no longer bounds anything.
The same bound is readable from a rule. The constraint written on a
declaration is available as versionConstraint, Gradle's
VersionConstraint.
For a module declared without a version, the constraints fixed for that module
by the consumed platforms are available as platformVersionConstraints. It
is a list rather than a single value, because more than one consumed platform
can bound the same module. The query that finds candidates is deliberately
unbounded, so a rule that applies a declared bound reads it from
versionConstraint rather than restating it. It is null for a module no
declaration was matched to, such as one a substitution rule resolved to, so
guard for that. satisfiesDeclaredBound, the verdict a rule applied before the
property did, is deprecated and will be removed in a later release.
The buildscript classpath is the exception. There a dynamic required version
bounds the candidate, so a plugin declared as version "[1.0, 2[" or
version "1.+" is not offered 2.0, whether the version was written in the
plugins block or came from a version catalog alias. Gradle flattens an alias to
a bare required version on the marker it synthesizes, leaving the two forms
identical, and a plugin has one declaration, so the interval is the version
declared rather than a floor something else may push past. Where a transitive
requirement on a classpath has pushed the selection past the interval, the
version already resolved lies outside the bound, and the bound is not applied,
as above.
A module declared without a version is additionally bound by the version a
consumed platform sets for it, since the build cannot take that upgrade without
also bumping the platform. Given a platform BOM that pins
log4j-core to 2.16.0:
dependencies {
api(platform("org.apache.logging.log4j:log4j-bom:2.16.0"))
api("org.apache.logging.log4j:log4j-core")
}
dependencies {
api platform('org.apache.logging.log4j:log4j-bom:2.16.0')
api 'org.apache.logging.log4j:log4j-core'
}
log4j-core is bounded at 2.16.0 even though versionConstraint is empty
for it, because the bound comes from the platform instead:
The following dependencies are using the latest milestone version:
- org.apache.logging.log4j:log4j-core:2.16.0
constrained by the platform org.apache.logging.log4j:log4j-bom
The following dependencies have later milestone versions:
- org.apache.logging.log4j:log4j-bom [2.16.0 -> 2.17.0]
The platform behind the bound appears on an attribution line under the bounded
entry, so the reason for the version shows up next to it. A platform project
appears as its build tree path, constrained by the platform :platform, and a
BOM as its group and module; where several platforms bound the module, all of
them follow constrained by the platforms. A constraint written as a range is
left out, since a range alone does not identify which version was selected.
The platform still has an entry of its own, so the upgrade that is actually
available, bumping the BOM, is still printed. A build that centralizes its
platforms in a platform project of its own, including one belonging to an
included build, declares the BOM somewhere this report does not cover. With
checkConstraints that BOM is reported here anyway, so the coordinate to bump
is printed either way (see Constraints). A bound the build
declares on the platform itself applies to that entry too: where a BOM's own
version is strictly "[2.0, 3.0[", inline or through a version catalog, the
newest version inside that range is reported and never the major beyond it.
The bound is deliberately narrow:
strictly range.prefer alone does not bound, the same as
a declared prefer.enforcedPlatform's own version into a strictly, so the
rule bounds the platform itself at that version and a newer BOM stops being
offered; a plain platform keeps its own upgrade line.Three things to know before writing a rule against a declared bound. Rejecting
every candidate for a module, including the version it currently resolves to,
does not fail the build: the module is reported in the unresolved section
instead, and its upgrade line with it. A bound on a version that was never
published has that effect, so a strictly "1.2.3" matching nothing is reported
as a resolution failure rather than as up to date.
A resolutionStrategy that throws while the plugin applies it to a
configuration, rather than while Gradle resolves the graph, skips that
configuration instead: its dependencies are absent from every section, the
skipped configuration is listed in the report's skipped section along with
the failure, and a warning is printed to the console. The build still succeeds.
Narrowing the candidate set can also surface metadata that the unbounded query
skipped over, with the same result. And where a module's only declaration is a
constraint, that constraint is reported as its current version, so
currentVersion may read [2.0, 3.1[ rather than a resolved version.
The declared strictVersion, requiredVersion, preferredVersion and
rejectedVersions remain available on versionConstraint for rules that need
something other than the bound.
The gradleReleaseChannel task property controls which release channel of the
Gradle project is used to check for available Gradle updates. Options are:
currentrelease-candidatenightlyThe default is release-candidate. The value can be changed as shown below:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
gradleReleaseChannel = "current"
}
tasks.named("dependencyUpdates").configure {
gradleReleaseChannel = "current"
}
The gradleVersionsApiBaseUrl task property provides an option for
customization of the Gradle versions service URL. If not specified, the default
value https://services.gradle.org/versions/ is used. The customization can be
useful in restricted environments without direct internet access and proxy
availability.
The dependencyUpdates task takes several optional parameters to adjust its
behavior. For an ad hoc run, change one with its command line
option (e.g. --revision release). The revision,
gradleReleaseChannel, outputFormatter, outputDir, and reportfileName
properties may also be set as system properties (e.g. -Drevision=release),
which predate the options and are kept for the builds that use them; where both
are set, the option is the one that applies:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
checkForGradleUpdate = true
outputFormatter = "json"
outputDir = "build/dependencyUpdates"
reportfileName = "report"
}
tasks.named("dependencyUpdates").configure {
checkForGradleUpdate = true
outputFormatter = "json"
outputDir = "build/dependencyUpdates"
reportfileName = "report"
}
checkForGradleUpdateWhether the report includes the Gradle releases the build could upgrade to, which are read from the Gradle versions API over the network for each release channel consulted. Turning it off skips those requests, which suits a build that runs offline or in a restricted environment:
./gradlew dependencyUpdates --no-check-for-gradle-update
outputDirThe directory the report file is written into. A relative path is resolved
against the project directory and an absolute one is used as given. The default
is dependencyUpdates under the project's build directory, which is
build/dependencyUpdates in a standard layout.
reportfileNameThe report file's name, without an extension. Each formatter supplies its own,
so report becomes report.txt, report.json, report.xml or report.html.
Naming more than one formatter writes one file per format, all sharing this
name.
The task property outputFormatter controls the report output format. The
following values are supported:
"plain": format output file as plain text (default)"json": format output file as json text"xml": format output file as xml text, can be used by other plugins (e.g. sonar)"html": format output file as htmlClosure: will be called with the result of the dependency update analysis
(from Kotlin, use the outputFormatter(Action<Result>) function instead)The console summary is printed at the lifecycle log level, so --quiet suppresses
it. The report file is still written; read it, or drop --quiet, if a script was
piping the console output.
You can also set multiple output formats using comma as the separator:
./gradlew dependencyUpdates -Drevision=release -DoutputFormatter=json,xml,html
The task property outputDir controls the output directory for the report
file(s). The directory will be created if it does not exist. The default value
is set to build/dependencyUpdates
./gradlew dependencyUpdates -Drevision=release -DoutputFormatter=json -DoutputDir=/any/path/with/permission
Last the property reportfileName sets the filename (without extension) of the
generated report. It defaults to report. The extension will be set according
to the used output format.
./gradlew dependencyUpdates -Drevision=release -DoutputFormatter=json -DreportfileName=myCustomReport
Sample output in each format:
------------------------------------------------------------
: Project Dependency Updates (report to plain text file)
------------------------------------------------------------
The following dependencies are using the latest milestone version:
- backport-util-concurrent:backport-util-concurrent:3.1
- backport-util-concurrent:backport-util-concurrent-java12:3.1
- io.github.ben-manes:gradle-versions-plugin:0.55.0
The following dependencies exceed the version found at the milestone revision level:
- com.google.guava:guava-tests [99.0-SNAPSHOT <- 23.3-jre]
https://github.com/google/guava
The following dependencies have later milestone versions:
- com.google.guava:guava [15.0 -> 23.0]
https://github.com/google/guava
- com.google.inject:guice [2.0 -> 7.0.0]
https://github.com/google/guice
- com.google.inject.extensions:guice-multibindings [2.0 -> 4.2.3]
https://github.com/google/guice
- com.linecorp.armeria:armeria [0.90.0 -> 1.40.0]
https://armeria.dev/
- io.zipkin.brave:brave [5.7.0 -> 6.3.1]
https://github.com/openzipkin/brave/brave
- org.springframework.boot:spring-boot-dependencies [1.5.8.RELEASE -> 4.1.0]
https://spring.io/projects/spring-boot
Failed to compare versions for the following dependencies because they were declared without version:
- com.google.code.gson:gson
Failed to determine the latest version for the following dependencies (use --info for details):
- com.github.ben-manes:unresolvable:1.0
- com.github.ben-manes:unresolvable2:1.0
- com.google.guava:guava:15.0
https://github.com/google/guava
- dom4j:dom4j
Gradle release-candidate updates:
- Gradle: [8.4 -> 9.7.0]
Alternatively, the report may be output to a structured file.
{
"count": 15,
"current": {
"count": 3,
"dependencies": [
{
"group": "backport-util-concurrent",
"name": "backport-util-concurrent",
"version": "3.1",
"projectUrl": "http://backport-jsr166.sourceforge.net/",
"userReason": null
},
{
"group": "backport-util-concurrent",
"name": "backport-util-concurrent-java12",
"version": "3.1",
"projectUrl": "http://backport-jsr166.sourceforge.net/",
"userReason": null
},
{
"group": "io.github.ben-manes",
"name": "gradle-versions-plugin",
"version": "0.55.0",
"projectUrl": null,
"userReason": null
}
]
},
"outdated": {
"count": 6,
"dependencies": [
{
"group": "com.google.guava",
"name": "guava",
"version": "15.0",
"projectUrl": "https://github.com/google/guava",
"userReason": null,
"available": {
"release": null,
"milestone": "23.0",
"integration": null
}
},
{
"group": "com.google.inject",
"name": "guice",
"version": "2.0",
"projectUrl": "https://github.com/google/guice",
"userReason": null,
"available": {
"release": null,
"milestone": "7.0.0",
"integration": null
}
},
{
"group": "com.google.inject.extensions",
"name": "guice-multibindings",
"version": "2.0",
"projectUrl": "https://github.com/google/guice",
"userReason": null,
"available": {
"release": null,
"milestone": "4.2.3",
"integration": null
}
},
{
"group": "com.linecorp.armeria",
"name": "armeria",
"version": "0.90.0",
"projectUrl": "https://armeria.dev/",
"userReason": null,
"available": {
"release": null,
"milestone": "1.40.0",
"integration": null
}
},
{
"group": "io.zipkin.brave",
"name": "brave",
"version": "5.7.0",
"projectUrl": "https://github.com/openzipkin/brave/brave",
"userReason": null,
"available": {
"release": null,
"milestone": "6.3.1",
"integration": null
}
},
{
"group": "org.springframework.boot",
"name": "spring-boot-dependencies",
"version": "1.5.8.RELEASE",
"projectUrl": "https://spring.io/projects/spring-boot",
"userReason": null,
"available": {
"release": null,
"milestone": "4.1.0",
"integration": null
}
}
]
},
"exceeded": {
"count": 1,
"dependencies": [
{
"group": "com.google.guava",
"name": "guava-tests",
"version": "99.0-SNAPSHOT",
"projectUrl": "https://github.com/google/guava",
"userReason": null,
"latest": "23.3-jre"
}
]
},
"undeclared": {
"count": 1,
"dependencies": [
{
"group": "com.google.code.gson",
"name": "gson",
"version": null,
"projectUrl": null,
"userReason": null
}
]
},
"unresolved": {
"count": 4,
"dependencies": [
{
"group": "com.github.ben-manes",
"name": "unresolvable",
"version": "1.0",
"projectUrl": null,
"userReason": null,
"reason": "Could not find any matches for com.github.ben-manes:unresolvable:+ as no versions of com.github.ben-manes:unresolvable are available.\nSearched in the following locations:\n - https://repo.maven.apache.org/maven2/com/github/ben-manes/unresolvable/maven-metadata.xml"
},
{
"group": "com.github.ben-manes",
"name": "unresolvable2",
"version": "1.0",
"projectUrl": null,
"userReason": null,
"reason": "Could not find any matches for com.github.ben-manes:unresolvable2:+ as no versions of com.github.ben-manes:unresolvable2 are available.\nSearched in the following locations:\n - https://repo.maven.apache.org/maven2/com/github/ben-manes/unresolvable2/maven-metadata.xml"
},
{
"group": "com.google.guava",
"name": "guava",
"version": "15.0",
"projectUrl": "https://github.com/google/guava",
"userReason": null,
"reason": "Could not resolve com.google.guava:guava:+."
},
{
"group": "dom4j",
"name": "dom4j",
"version": "none",
"projectUrl": null,
"userReason": null,
"reason": "Could not resolve dom4j:dom4j:+."
}
]
},
"gradle": {
"enabled": true,
"running": {
"isFailure": false,
"isUpdateAvailable": false,
"reason": "",
"version": "8.4"
},
"current": {
"isFailure": false,
"isUpdateAvailable": true,
"reason": "",
"version": "9.7.0"
},
"releaseCandidate": {
"isFailure": false,
"isUpdateAvailable": false,
"reason": "update check succeeded: no release available",
"version": ""
},
"nightly": {
"isFailure": false,
"isUpdateAvailable": false,
"reason": "update check disabled",
"version": ""
}
},
"skipped": {
"count": 1,
"configurations": [
{
"project": ":",
"name": "compileClasspath",
"reason": "org.gradle.api.InvalidUserCodeException: Could not add a component selection rule for module 'com.google.guava'."
}
]
}
}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
<count>15</count>
<current>
<count>3</count>
<dependencies>
<dependency>
<group>backport-util-concurrent</group>
<name>backport-util-concurrent</name>
<version>3.1</version>
<projectUrl>http://backport-jsr166.sourceforge.net/</projectUrl>
</dependency>
<dependency>
<group>backport-util-concurrent</group>
<name>backport-util-concurrent-java12</name>
<version>3.1</version>
<projectUrl>http://backport-jsr166.sourceforge.net/</projectUrl>
</dependency>
<dependency>
<group>io.github.ben-manes</group>
<name>gradle-versions-plugin</name>
<version>0.55.0</version>
</dependency>
</dependencies>
</current>
<outdated>
<count>6</count>
<dependencies>
<outdatedDependency>
<group>com.google.guava</group>
<name>guava</name>
<version>15.0</version>
<projectUrl>https://github.com/google/guava</projectUrl>
<available>
<milestone>23.0</milestone>
</available>
</outdatedDependency>
<outdatedDependency>
<group>com.google.inject</group>
<name>guice</name>
<version>2.0</version>
<projectUrl>https://github.com/google/guice</projectUrl>
<available>
<milestone>7.0.0</milestone>
</available>
</outdatedDependency>
<outdatedDependency>
<group>com.google.inject.extensions</group>
<name>guice-multibindings</name>
<version>2.0</version>
<projectUrl>https://github.com/google/guice</projectUrl>
<available>
<milestone>4.2.3</milestone>
</available>
</outdatedDependency>
<outdatedDependency>
<group>com.linecorp.armeria</group>
<name>armeria</name>
<version>0.90.0</version>
<projectUrl>https://armeria.dev/</projectUrl>
<available>
<milestone>1.40.0</milestone>
</available>
</outdatedDependency>
<outdatedDependency>
<group>io.zipkin.brave</group>
<name>brave</name>
<version>5.7.0</version>
<projectUrl>https://github.com/openzipkin/brave/brave</projectUrl>
<available>
<milestone>6.3.1</milestone>
</available>
</outdatedDependency>
<outdatedDependency>
<group>org.springframework.boot</group>
<name>spring-boot-dependencies</name>
<version>1.5.8.RELEASE</version>
<projectUrl>https://spring.io/projects/spring-boot</projectUrl>
<available>
<milestone>4.1.0</milestone>
</available>
</outdatedDependency>
</dependencies>
</outdated>
<exceeded>
<count>1</count>
<dependencies>
<exceededDependency>
<group>com.google.guava</group>
<name>guava-tests</name>
<version>99.0-SNAPSHOT</version>
<projectUrl>https://github.com/google/guava</projectUrl>
<latest>23.3-jre</latest>
</exceededDependency>
</dependencies>
</exceeded>
<undeclared>
<count>1</count>
<dependencies>
<dependency>
<group>com.google.code.gson</group>
<name>gson</name>
</dependency>
</dependencies>
</undeclared>
<unresolved>
<count>4</count>
<dependencies>
<unresolvedDependency>
<group>com.github.ben-manes</group>
<name>unresolvable</name>
<version>1.0</version>
<reason>Could not find any matches for com.github.ben-manes:unresolvable:+ as no versions of com.github.ben-manes:unresolvable are available.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/com/github/ben-manes/unresolvable/maven-metadata.xml</reason>
</unresolvedDependency>
<unresolvedDependency>
<group>com.github.ben-manes</group>
<name>unresolvable2</name>
<version>1.0</version>
<reason>Could not find any matches for com.github.ben-manes:unresolvable2:+ as no versions of com.github.ben-manes:unresolvable2 are available.
Searched in the following locations:
- https://repo.maven.apache.org/maven2/com/github/ben-manes/unresolvable2/maven-metadata.xml</reason>
</unresolvedDependency>
<unresolvedDependency>
<group>com.google.guava</group>
<name>guava</name>
<version>15.0</version>
<projectUrl>https://github.com/google/guava</projectUrl>
<reason>Could not resolve com.google.guava:guava:+.</reason>
</unresolvedDependency>
<unresolvedDependency>
<group>dom4j</group>
<name>dom4j</name>
<version>none</version>
<reason>Could not resolve dom4j:dom4j:+.</reason>
</unresolvedDependency>
</dependencies>
</unresolved>
<skipped>
<count>1</count>
<configurations>
<skippedConfiguration>
<project>:</project>
<name>compileClasspath</name>
<reason>org.gradle.api.InvalidUserCodeException: Could not add a component selection rule for module 'com.google.guava'.</reason>
</skippedConfiguration>
</configurations>
</skipped>
<gradle>
<enabled>true</enabled>
<running>
<version>8.4</version>
<isUpdateAvailable>false</isUpdateAvailable>
<isFailure>false</isFailure>
<reason/>
</running>
<current>
<version>9.7.0</version>
<isUpdateAvailable>true</isUpdateAvailable>
<isFailure>false</isFailure>
<reason/>
</current>
<releaseCandidate>
<version/>
<isUpdateAvailable>false</isUpdateAvailable>
<isFailure>false</isFailure>
<reason>update check succeeded: no release available</reason>
</releaseCandidate>
<nightly>
<version/>
<isUpdateAvailable>false</isUpdateAvailable>
<isFailure>false</isFailure>
<reason>update check disabled</reason>
</nightly>
</gradle>
</response>
If you need to create a report in a custom format, you can provide a formatter
function to the dependencyUpdates task's outputFormatter. The formatter
receives the analysis as an instance of
com.github.benmanes.gradle.versions.reporter.result.Result:
in the Kotlin DSL it is the receiver of the formatter block, and in Groovy it is
passed as the closure argument.
[!IMPORTANT] Under the configuration cache, the formatter cannot reach the project or the build script from inside the closure, because it runs at execution time. Read what it needs into local variables beforehand, as shown in Migrating from prior versions. Gradle's configuration cache requirements cover the underlying rules.
For example, if you wanted to create an html table for the upgradable dependencies, you could use:
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
tasks.named<DependencyUpdatesTask>("dependencyUpdates") {
outputFormatter {
val updatable = outdated.dependencies
if (updatable.isNotEmpty()) {
val table = buildString {
appendLine("<table>")
appendLine(" <thead>")
appendLine(" <tr><td>Group</td><td>Module</td><td>Current version</td><td>Latest version</td></tr>")
appendLine(" </thead>")
appendLine(" <tbody>")
updatable.forEach { dependency ->
appendLine(
" <tr><td>${dependency.group}</td><td>${dependency.name}</td>" +
"<td>${dependency.version}</td>" +
"<td>${dependency.available.release ?: dependency.available.milestone}</td></tr>"
)
}
appendLine(" </tbody>")
appendLine("</table>")
}
println(table)
}
}
}
tasks.named("dependencyUpdates").configure {
outputFormatter = { result ->
def updatable = result.outdated.dependencies
if (!updatable.isEmpty()) {
def table = new StringBuilder()
table.append("<table>\n")
table.append(" <thead>\n")
table.append(" <tr><td>Group</td><td>Module</td><td>Current version</td><td>Latest version</td></tr>\n")
table.append(" </thead>\n")
table.append(" <tbody>\n")
updatable.each { dependency ->
table.append(" <tr><td>${dependency.group}</td><td>${dependency.name}</td>")
table.append("<td>${dependency.version}</td>")
table.append("<td>${dependency.available.release ?: dependency.available.milestone}</td></tr>\n")
}
table.append(" </tbody>\n")
table.append("</table>")
println table
}
}
}
Running the task in the root project generates one merged report covering every project. The report is aggregated from a task in each project, so it works with parallel execution, the configuration cache, and configure on demand. Under the configuration cache the dependency metadata read during resolution is tracked as an input, so a newly published version invalidates the cached entry rather than serving a stale report.
The merged report and the partial results it merges are written to the
aggregating project's build directory. clean removes them only if that project
has a clean task, which the base plugin supplies. A root project that
applies no other plugin can add base for that alone; the task itself does not
need it.
"build.gradle.kts":
plugins {
base
}
"build.gradle":
plugins {
id 'base'
}
When a coordinate's declared version differs across the aggregated projects, the projects that declared each version are printed in the plain text, JSON, XML, and HTML reports, so the entry no longer reads as self-contradictory:
The following dependencies have later milestone versions:
- org.jacoco:org.jacoco.ant [0.8.14 -> 0.8.15]
declared in root project
One declared version can also have different latest versions across the projects, which happens when a platform bounds the module in some of them and not in others. Each of those latest versions is shown on an entry of its own, with the projects included the same way, rather than the newest of them being shown for every project:
The following dependencies are using the latest milestone version:
- com.google.inject:guice:2.0
constrained by the platform :platform in root project
The following dependencies have later milestone versions:
- com.google.inject:guice [2.0 -> 7.0.0]
declared in :platform
So a coordinate's group and name can appear on two entries, and in two sections of one report; the projects printed on each are what distinguish them.
The plain text and HTML reports print the first five projects and a count of the
rest (declared in :app, :lib, ... and 60 others). The JSON and XML reports
always include the complete list, so use one of them when a tool needs every
project.
A project is printed as its path in the build tree, so a project of an included
build reads as :child:app and that build's root as :child. In each build the
root project's own path is :, which would otherwise put the projects of two
builds under one name.
With the settings plugin applied (see Applying the
plugin), the dependencyUpdates task is registered in
the root project and every other project contributes to it. No project applies
a plugin itself, and a root build script is not required—add one only if you
want to configure the task. The report also covers the plugins the settings
script declares, which appear in no project's buildscript; a version pinned in
pluginManagement for a plugin the build never applies is not reported.
The settings plugin registers the root project's dependencyUpdates task
before the root build script runs, so a build script that registers its own
task by that name now fails with a duplicate-task error—rename yours.
The settings that control resolution (revision, rejectVersionIf or a full
resolutionStrategy, filterConfigurations, filterDeclaredConfigurations,
checkConstraints, checkBuildEnvironmentConstraints, and
rejectOutOfBoundVersions) are inherited from
the nearest project up the hierarchy whose task set them. Configuring the root
project's task therefore covers every project, unless a subproject configures
its own (see Task properties).
An included build is a separate build with its own settings script, so its
projects are not part of this build's report. Apply the settings plugin in the
included build's settings script as well, and run its dependencyUpdates task
separately. buildSrc is a separate build too, and is likewise excluded. This
is not specific to the settings plugin—an included build has never been covered
by default.
To report on every build in one invocation, register a lifecycle task that depends on each included build's task. Each build writes its own report, which suits builds that are developed independently, as each keeps its own settings:
"build.gradle.kts":
tasks.register("allDependencyUpdates") {
gradle.includedBuilds.forEach { dependsOn(it.task(":dependencyUpdates")) }
}
"build.gradle":
tasks.register("allDependencyUpdates") {
gradle.includedBuilds.each { dependsOn(it.task(':dependencyUpdates')) }
}
Every included build needs the plugin applied for its dependencyUpdates task
to exist. A build that must stay unmodified can have the plugin injected by an
init script instead.
An included build's project can instead be merged into this build's report, by
declaring it in the dependencyUpdatesAggregation configuration of the project
that aggregates. Declare it by the coordinates that the include substitutes, and
apply the plugin in the included build, so that a partial result exists to
merge. Each declaration merges the one project it resolves to. A project of this
build that the aggregating project's own tree does not cover, such as a sibling,
is declared the same way:
"build.gradle.kts":
dependencies {
dependencyUpdatesAggregation("com.example:child:1.0")
}
"build.gradle":
dependencies {
dependencyUpdatesAggregation 'com.example:child:1.0'
}
A project can have a dependencyUpdates task of its own, reporting on itself
and its subprojects (see Other ways to apply the
plugin). Run it by its path to get just that
report:
./gradlew :subproject:dependencyUpdates
Under isolated projects a project plugin cannot register a task in another project, so a project only contributes to the aggregate report if it applies a plugin itself. The settings plugin covers this: it applies a plugin to each project as the project is evaluated, so the recommended setup works unchanged (see Applying the plugin).
A build that cannot apply the settings plugin can still cover every project (see Contributor plugin).
Under isolated projects, contributing projects that share a group and name are aggregated as one, and a warning is printed to the console for any project missing from the report.
The settings plugin is the recommended way to apply the plugin (see Applying the plugin). The options below cover builds that need something different:
plugins
block and legacy plugin application);In the snippets below, replace $version with the current release, shown in the
badge at the top of this page.
[!IMPORTANT] When the settings plugin is also applied, request the per-project plugin without a version—the settings plugin already puts it on every project's classpath, and a versioned request fails to resolve. This includes a version catalog alias, which always includes a version.
plugins block"build.gradle.kts":
plugins {
id("io.github.ben-manes.versions") version "$version"
}
"build.gradle":
plugins {
id "io.github.ben-manes.versions" version "$version"
}
[!TIP] Prefer the
pluginsblock—it is the modern replacement forbuildscript-based plugin application.
"build.gradle.kts":
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath("io.github.ben-manes:gradle-versions-plugin:$version")
}
}
apply(plugin = "io.github.ben-manes.versions")
"build.gradle":
buildscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath "io.github.ben-manes:gradle-versions-plugin:$version"
}
}
apply plugin: "io.github.ben-manes.versions"
A build that cannot apply the settings plugin—under isolated projects, where a
project plugin cannot register a task in another project (see Isolated
projects)—can keep applying io.github.ben-manes.versions
in the root project, and apply io.github.ben-manes.versions.contributor in
every other project, typically from a convention plugin they already share:
"buildSrc/src/main/kotlin/my-conventions.gradle.kts":
plugins {
id("io.github.ben-manes.versions.contributor")
}
"buildSrc/src/main/groovy/my-conventions.gradle":
plugins {
id 'io.github.ben-manes.versions.contributor'
}
The convention plugin's own build must have the plugin on its classpath, e.g. as
an implementation("io.github.ben-manes:gradle-versions-plugin:$version")
dependency in buildSrc/build.gradle.kts.
The contributor plugin registers only the producer that feeds the aggregate
report, so dependencyUpdates remains a single task in the root project. The
main plugin is a superset of the contributor plugin: a project that applies
io.github.ben-manes.versions instead still feeds the aggregate report, and
also has a dependencyUpdates task of its own, covering itself and its
subprojects.
You can also transparently add the plugin to every Gradle project that you run
via a
Gradle init script.
Apply the settings plugin from beforeSettings, which covers every project of
the build and works under isolated projects (see Isolated
projects). A dependencyUpdates task is registered in
every build that runs, so an included build is reported without being modified
(see Composite builds):
"$HOME/.gradle/init.d/add-versions-plugin.init.gradle.kts":
import com.github.benmanes.gradle.versions.VersionsSettingsPlugin
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
initscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath("io.github.ben-manes:gradle-versions-plugin:+")
}
}
gradle.beforeSettings(Action<Settings> {
pluginManager.apply(VersionsSettingsPlugin::class.java)
})
gradle.rootProject(Action<Project> {
tasks.withType(DependencyUpdatesTask::class.java).configureEach {
// configure the task, for example wrt. resolution strategies
}
})
"$HOME/.gradle/init.d/add-versions-plugin.gradle":
import com.github.benmanes.gradle.versions.VersionsSettingsPlugin
import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
initscript {
repositories {
gradlePluginPortal()
}
dependencies {
classpath 'io.github.ben-manes:gradle-versions-plugin:+'
}
}
beforeSettings { settings ->
settings.pluginManager.apply(VersionsSettingsPlugin)
}
gradle.rootProject {
tasks.withType(DependencyUpdatesTask).configureEach {
// configure the task, for example wrt. resolution strategies
}
}
A script has no implicit import for the plugin's types, so the imports at the top of these snippets are required to reference them by their simple names.
An init script resolves the plugin on a classpath of its own, so a build that
applies the plugin itself ends up with a second copy of it. An init script runs
before the build's own scripts, so its copy is the one that registers the task
and the build's copy does nothing, which leaves such a build working as it did.
For the same reason the plugin is absent from the project's own classpath, so a
plugins block that requests it alongside an init script needs a version,
unlike one in a build whose settings script applies the settings plugin (see
Other ways to apply the plugin).
Have a look at
examples/kotlin
and
examples/groovy
# Publish the latest version of the plugin to mavenLocal()
$ ./gradlew publishToMavenLocal
# Try out the samples
$ ./gradlew -p examples/kotlin dependencyUpdates
$ ./gradlew -p examples/groovy dependencyUpdates
The plugin requires Gradle 8.4 or later, checked when the plugin is applied. It targets Java 8 bytecode, so it runs on any JVM that can run Gradle itself. Parallel execution, the configuration cache, configure on demand, and isolated projects (see Isolated projects) are supported.
To migrate to the current version, start at the section for the version your build is on and work upward. Each section migrates to the version covered by the section above it, and the topmost migrates to the current release. Importants are must-dos, Tips are actions you should or may want to take, and Notes are things worth knowing that need no action.
In the next release, the report is held to the bounds written in the build without a rule written for it, and a coordinate with one declared version and different latest versions across the aggregated projects is shown on one entry per latest version, where the entries were merged into the newest of them before:
[!IMPORTANT]
- A candidate outside a
strictlyorrejectbound written in the build, outside a dynamic version declared on the buildscript classpath, or outside the version fixed by a consumed platform, is left out of the report, where it was listed before. SetrejectOutOfBoundVersions = falseto list it again, or pass--no-reject-out-of-bound-versionsfor a single run (see Respecting declared bounds).- A coordinate's group and name can now appear on two entries of one report, and in two of its sections. The projects for each entry are included in
projects(see Multi-project builds), which is what distinguishes them. A tool that keys the entries by group and name alone has to key them by the projects as well.
[!TIP]
- Drop
!satisfiesDeclaredBoundfrom arejectVersionIfrule, since the bound is now applied byrejectOutOfBoundVersions. The member is deprecated and will be removed in a later release; a warning is printed once per project when a rule reads it, and a Kotlin DSL build that treats compiler warnings as errors has to drop the clause before upgrading. With the clause still in a rule, the same candidates are rejected under--no-reject-out-of-bound-versionsas without it.
[!NOTE]
- The presence of a version catalog no longer changes whether a plugin versioned inline as a range in the
pluginsblock is offered an upgrade past that range. The upgrade was withheld when an unused alias for the same plugin was present in the catalog and offered when it was not (see Respecting declared bounds).- A module that a platform bounds in one project and not in another is now up to date in the bounded project and outdated in the other, rather than outdated in both. The merged entry printed an upgrade that is not available in the bounded project.
- A split entry can include an attribution line that the merged one left out. The line is left out when a project outside the platform's importers declares the module. Once the entries are split, that project is on an entry of its own, so the line is printed again (see Report format).
In v0.61.0, a project is printed as its path in the build tree wherever one
appears in the report, so the projects of an included build no longer share the
: that stands for each build's own root. The platforms a build imports through
its own platform projects are reported, as are the configurations left
uninspected by a resolutionStrategy that throws:
[!IMPORTANT]
- A project of an included build is printed as its build tree path, so an entry reads
declared in :childwhere it readdeclared in root project(see Multi-project builds). The same paths appear in the JSON and XML reports, inprojects.- A
buildSrcbuild is printed the same way when its task is run from the outer build, as:buildSrc:dependencyUpdates. Its report is headed:buildSrcrather than:, and its projects are printed beneath that path. Running the task from insidebuildSrcmakes it the root of its own build tree, which still reads:.- An attribution line reading
imported by the platform :platformscan now be printed under an entry, showing the platform projects the build imports the dependency through. A tool that parses the plain text report line by line has to skip it, as it already does for the other attribution lines (see Report format). The same paths appear in the JSON and XML reports, inplatformProjects.- The plain text report now has a section listing the configurations that could not be inspected, after the dependency ones, where they were logged at info level and left out silently before. They appear in the JSON and XML reports under
skipped, whichcountdoes not include, so a tool totalling the report has to leave it out too (see Report output).- A custom
outputFormatterthat callscopyonDependencyOutdated,DependencyLatestorDependencyUnresolvedhas to be recompiled against this release. Each gained aplatformProjectsproperty, and a data class has exactly onecopy, so the one earlier releases shipped is gone. Their constructors are unchanged: a formatter that only reads the report, as the documented ones do, needs nothing.
[!TIP] An entry can show a configuration name that
filterConfigurationscannot match, such as a declarable configuration read through a resolvable classpath that extends it. Rejecting the classpath instead removes the build's own dependencies with it.filterDeclaredConfigurationsrejects the entry by the name it shows, and leaves what the task checks alone.
[!NOTE]
- A dependency that two builds of a composite declare at different versions is now reported as divergent. The attribution showing the projects for each version was left out while every entry had the same project path, which in a composite was always
:.- With
checkConstraints, an entry is now printed for each platform the build's own platform projects import. The modules those constraints bound were reported as up to date, with nothing to show the coordinate to bump (see Constraints).- A configuration that declares a platform is resolved a second time, to find those imports, so a
beforeResolvehook on it runs once more than it did. A configuration declaring no platform resolves nothing extra.
In v0.60.0, the configuration a dependency was declared directly against is printed, so a plugin that fills a classpath of its own when it is applied no longer reads as the build declaring the dependency. The reason a dependency constraint was declared with is printed too, and a component selection rule can keep the report inside the bound the build declared:
[!IMPORTANT]
- An attribution line can now be printed under an entry that had none, showing the configuration the dependency was declared against. A tool that parses the plain text report line by line has to skip it, as it already does for the other attribution lines (see Report format). The same names appear in the JSON and XML reports, in
configurations, which until now contained only what a plugin contributed, so a tool reading that field has to readcontributedalongside it to tell the two apart.- The reason on a dependency constraint declared with
becauseis now printed, where only a dependency's reason appeared before. It prints on the same line as a dependency's reason does, so a tool that already handles one handles both.
[!TIP]
- A component selection rule can keep the report inside what the build declared, with
rejectVersionIf { !satisfiesDeclaredBound }. A module bounded by astrictlyor by a platform the build consumes is then bounded there, so only versions the build can actually take are offered (see Respecting declared bounds).- A Kotlin DSL build script that worked around the Gradle 9 overload ambiguity by spelling out
Action<ComponentSelectionWithCurrent>can go back to the untypedall { }andwithModule(id) { }forms.
In v0.59.0, the version another resolution found for a dependency that failed
to resolve is reported, and the partial result of every project is collected
under the project that aggregates them, so no build directory is created in a
project that exists only for a nested include:
[!IMPORTANT]
- A declared version that resolved in one place and failed in another is now reported twice: once with the version found for it, and again as unresolved. A dependency declared without a version doubles the same way, as undeclared and unresolved. The JSON and XML reports count it in each place. A tool reading
countas a dependency total, or treating the sections as disjoint, has to allow for the overlap (see Report format).- The unresolved section of the plain text report now includes the declared version, as every other section does. A tool that parses that section line by line has to allow it.
[!TIP] Run
./gradlew dependencyUpdates --clean-legacy-partialsonce to remove thebuild/dependencyUpdates/partial.jsonthat earlier releases wrote into each project. Thecleantask removes it from a project that directly or indirectly applies the Base plugin, but a project with no build script has nocleantask.
In v0.58.0, the reporters print through the build's logger, and attribution lines are added to the reports:
[!IMPORTANT]
- The console summary now prints at the lifecycle log level, so
--quietsuppresses it. The report file is still written; read it, or drop--quiet, if a script was piping the console output (see Report format).- An indented attribution line may be printed under an entry, showing the projects that declared a divergent version (see Multi-project builds) or the configuration a plugin contributed it into (see The
dependencyUpdatestask). A tool that parses the plain text report line by line has to skip them; the same information appears as fields in the JSON and XML reports.
In v0.57.0 the settings plugin can be applied from an init script:
[!IMPORTANT] An init script that applies
VersionsPlugintoallprojectsreports per project rather than once, omits the plugins that the settings script declares, and fails under isolated projects. Apply the settings plugin frombeforeSettingsinstead (see Initialization script).
In v0.56.0 the settings plugin is added, and it becomes the recommended setup:
[!TIP] Apply
io.github.ben-manes.versions.settingsin the settings script (see Applying the plugin) and removeio.github.ben-manes.versionsfrom your build scripts. A project that keeps the main plugin for a separate per-project report must request it without a version, because the settings plugin already puts the plugin on every project's classpath.
[!NOTE]
- A build that applied
io.github.ben-manes.versions.contributorfrom a convention plugin for isolated projects support no longer needs it: the settings plugin covers every project. The contributor plugin remains available for builds that cannot apply a settings plugin.- Task configuration is unchanged: configure
dependencyUpdatesin the root build script as before.
In v0.55.0 the plugin moves from the com.github.ben-manes namespace to
io.github.ben-manes, and the minimum supported Gradle version rises to 8.4:
[!IMPORTANT]
- Move a
buildscriptorinitscriptclasspathdependency to theio.github.ben-manes:gradle-versions-plugincoordinate. v0.54.0 is the last release published undercom.github.ben-manes:gradle-versions-plugin, so no further updates are published under the old coordinate.- Under the configuration cache, a custom
outputFormattercannot reach the project or the build script from inside the closure, because it runs at execution time (see Report format). Read what it needs into local variables inside theconfigureblock, and use thePlainTextReporterconstructor that takes the project path, as shown below.Kotlin
"build.gradle.kts":
import com.github.benmanes.gradle.versions.reporter.PlainTextReporter import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask tasks.named<DependencyUpdatesTask>("dependencyUpdates").configure { val projectPath = project.path outputFormatter { PlainTextReporter(projectPath, revision, gradleReleaseChannel).write(System.out, this) } }Groovy
"build.gradle":
import com.github.benmanes.gradle.versions.reporter.PlainTextReporter tasks.named("dependencyUpdates").configure { def projectPath = project.path def taskRevision = revision def releaseChannel = gradleReleaseChannel outputFormatter { result -> new PlainTextReporter(projectPath, taskRevision, releaseChannel).write(System.out, result) } }Groovy also needs
revisionandgradleReleaseChannelread up front, because the closure is coerced to anActionwithout a delegate. In a precompiled script plugin a top-levelvalis a field of the script, so hoisting the value out of theconfigureblock does not work.
[!TIP] Switch the plugin ID from
com.github.ben-manes.versionstoio.github.ben-manes.versions. The legacy ID is deprecated but keeps receiving releases, so this can happen at your convenience; only the main plugin has a legacy ID.
[!NOTE] v0.55.0 also reworks how the merged report of a multi-project build is produced: it is aggregated from a task in each project, which adds support for parallel execution, the configuration cache, and isolated projects (see Multi-project builds). The report content is unchanged, as is task configuration apart from a custom
outputFormatterunder the configuration cache.
Then continue with the v0.55.0 steps above.
This plugin only reports. To apply the updates it finds automatically:
libs.versions.toml) based on
this plugin's reportOther related tools:
(top 30 of 79)
Groovy
66.3%
Kotlin
33.6%