# A Gradle plugin that rewrites your version catalog without touching your code

> version-catalog-update is a Gradle plugin that resolves newer versions for the dependencies in a libs.versions.toml catalog and rewrites the file in place, with an interactive mode that writes a separate updates file first. The trade is that it needs to resolve your whole dependency graph to work, so a project that cannot resolve dependencies cannot use it.

**littlerobots/version-catalog-update-plugin** — Gradle plugin for updating a project version catalog

- Repository: https://github.com/littlerobots/version-catalog-update-plugin
- Stars: 663 · Forks: 26
- Language: Kotlin
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/littlerobots-version-catalog-update-plugin

## The catalog file is the target, and rewriting it is the whole job

The problem is that a Gradle version catalog accumulates stale versions. Someone pins a library in libs.versions.toml, the library ships a new release, and the file drifts from reality until a build breaks on a yanked artefact. The plugin's stated purpose is to help keep the versions in a Gradle version catalog toml file up to date, and the mechanism is deliberately narrow. Running the task updates new available versions in the catalog while retaining existing version groups if they exist, sorts the keys used in the versions, libraries, bundles and plugins sections by name, and updates bundles so that they only contain valid keys, with the keys inside a bundle sorted. Versions that are not used and not marked to be kept are removed when the catalog is updated, and that removal is configurable. So the plugin does not just bump numbers. It also prunes and tidies, which is a reasonable default and also the behaviour most likely to surprise someone with a deliberate pin in place.

## Interactive mode is the safer path for a large jump

The README is direct about the risk. Updating all dependencies at once without testing is generally not recommended, and the plugin's answer is that a version control system can roll the catalog back by comparing a diff, but that for large updates this may be inconvenient, and in these cases interactive mode might help. Interactive mode changes what the plugin writes. When you run the task with the interactive flag, the libs.versions.toml file is not directly updated; instead a libs.versions.updates.toml file is created containing the entries that would be updated and any pinned entries that can be updated. That file uses the short form dependency notation without any version.ref values, pinned entries are commented out by default and all other entries are uncommented. You can skip an entry by commenting it out or removing it, and you can edit the entry if that is what you want. A second task then applies the changes, updating version.refs and versions in the same way the direct task would.```
# Version catalog updates generated at 2022-08-19T16:00:29.757349
#
# Contents of this file will be applied to libs.versions.toml when running versionCatalogApplyUpdates.
#
# Comments will not be applied to the version catalog when updating.
# To prevent a version upgrade, comment out the entry or remove it.
#
[libraries]
# @pinned version 4.9.3 (antlr) --> 4.10.1
#antlr = "org.antlr:antlr4:4.10.1"
# From version 2.5.4 (asciidoctorj) --> 2.5.5
asciidoctorj = "org.asciidoctor:asciidoctorj:2.5.5"
# From version 2.1.2 (asciidoctorjPdf) --> 2.1.6
``` The README notes that any comments and any other TOML tables than libraries and plugins are ignored when the changes are applied, which is a real limitation of the round trip.

## Version selection is a policy you write, not a switch you flip

The default behaviour is a stable version selected based on the version name, unless the current version is already considered unstable and a newer version exists. A version counts as stable if it is not a snapshot or an alpha version, and the README points at the VersionSelectors class for the exact definition. The selection can be replaced with your own predicate, and the example given is a check that rejects candidates whose version string contains SNAPSHOT or ALPHA, taking the ModuleVersionCandidate and returning true if the version is allowed.```groovy
    versionSelector {
        // here 'it' is a ModuleVersionCandidate that can be used to determine if the version
        // is allowed, returning true if it is.
        !(it.candidate.version.contains('SNAPSHOT') || it.candidate.version.contains('ALPHA'))
    }
``` There are also predefined selectors to import by name, with the comment describing them: LATEST accepts any latest version, STABLE selects only stable versions, and PREFER_STABLE, the default, accepts unstable versions only if the catalog already uses an unstable version. That last one is a sensible default for a mixed catalog, because it does not drag a stable entry into a nightly just because a newer nightly exists.

## The plugin needs to resolve your dependencies, and that shapes the setup

There is a requirement that catches people out, and the README gives it its own section. The plugin needs to be able to resolve the dependencies of your project, which means you have to configure repositories for the root project and not just for your subprojects. If dependencies cannot be resolved, the plugin will tell you. The easiest way to declare repositories for the project is to declare the dependencies in settings.gradle, and the README notes that plugins are resolved using the repositories declared in the pluginManagement configuration. That is the difference between a plugin that reads a file and a plugin that consults a resolver. It has to ask the repository what versions exist, which is why it needs credentials, network access and a coherent repository declaration across the whole build. The gradle.properties file in the repository root and the settings.gradle at the top show the shape of a project that uses the plugin itself.

## The plugin block and the Maven Central repository entry

Installation is a plugins block entry, and there are two ways to write it. In a Groovy build.gradle the plugin id is applied with a version, and in a Kotlin build.gradle.kts the same id is applied with the id and version in parentheses. The plugin is hosted on the Gradle Plugin portal and on Maven Central, and the README says snapshots are only published to Maven Central, so if you want to use a snapshot you have to include Maven Central in your plugin repositories. That entry goes in the pluginManagement block in settings.gradle, with a repositories block containing mavenCentral, and the same repository is needed in the build script when the plugins block is used, because the classpath dependency is the nl.littlerobots.vcu:plugin artifact at the matching version. The plugin id and the classpath coordinates differ, which is a small trap for anyone resolving the plugin by hand.```groovy
  id "nl.littlerobots.version-catalog-update" version "<latest version>"
``` The plugin requires Gradle 7.2 or up, and the README notes that version catalogs are an incubating Gradle feature for versions lower than 7.4, with a pointer to the documentation on enabling it.

## It replaced the versions plugin, and the change is visible in the API

The upgrading section explains a change that affects anyone migrating from an earlier release. Earlier versions of this plugin used the gradle-versions-plugin to resolve dependencies, and that plugin is no longer required, with version selection now configured using the versionSelector option. So the resolution logic moved inside the plugin rather than being delegated, which is why the selector configuration exists in the versionCatalogUpdate block and why the ModuleVersionCandidate predicate is now part of the public surface. The repository is a Kotlin project with a plugin/ directory, a catalog/ directory for its own version catalog, a spotless/ directory for formatting, a CHANGELOG.md, a LICENSE.txt and the usual Gradle wrapper. The recent releases are v1.1.1 from 2026-08-01, v1.1.0 from 2026-02-21 and v1.0.1 from 2025-09-27, so the 1.1 line has been the stable line for a while, and the last push to the repository was on 2026-09-18.

## Conclusion

version-catalog-update suits a Gradle project on version catalogs that has accumulated stale dependency versions and wants the update done by a tool rather than by hand, with a diff to review afterwards. It does not suit a project that cannot resolve its dependencies, because resolution is the mechanism it uses, and it does not suit a build that has deliberately pinned versions, since unused and unmarked versions are removed unless you configure otherwise. Before adopting, read the configuration section for the version selector and the removal behaviour, and note that the last push to the repository was on 2026-09-18 with v1.1.1 from 2026-08-01 under an Apache-2.0 licence.

## FAQ

### How do I add version-catalog-update to my Gradle build?

Apply the plugin id in the plugins block, as `id "nl.littlerobots.version-catalog-update" version "<latest version>"` in a Groovy build.gradle or `id("nl.littlerobots.version-catalog-update") version "<latest version>"` in a Kotlin build.gradle.kts. The plugin is on the Gradle Plugin portal and Maven Central, and requires Gradle 7.2 or up.

### How do I update my libs.versions.toml file?

Run `./gradlew versionCatalogUpdate`. It updates new available versions while retaining existing version groups, sorts the keys in the versions, libraries, bundles and plugins sections, and updates bundles to contain only valid keys. Unused versions that are not marked to be kept are removed, and that behaviour is configurable.

### What is interactive mode in version-catalog-update?

Running `./gradlew versionCatalogUpdate --interactive` leaves libs.versions.toml alone and writes a libs.versions.updates.toml file with the entries that would be updated, using short form notation without version.ref values and commenting out pinned entries by default. You apply the changes with `./gradlew versionCatalogApplyUpdates`, and comments and TOML tables other than libraries and plugins are ignored when applying.

### How do I control which versions version-catalog-update selects?

The default picks a stable version, where stable means not a snapshot or an alpha, unless the current version is already unstable and a newer one exists. You can supply your own predicate over the ModuleVersionCandidate, or use the predefined LATEST, STABLE and PREFER_STABLE selectors, with PREFER_STABLE as the default.

### Why does version-catalog-update fail on my project?

The plugin needs to resolve the dependencies of your project to find newer versions, so repositories must be configured for the root project rather than only for subprojects, and plugins are resolved through the repositories declared in the pluginManagement configuration. The README says the plugin will tell you if dependencies cannot be resolved.

## Sources

- [Issues](https://github.com/littlerobots/version-catalog-update-plugin/issues)
- [License: Apache-2.0](https://github.com/littlerobots/version-catalog-update-plugin/blob/main/LICENSE)
- [littlerobots/version-catalog-update-plugin on GitHub](https://github.com/littlerobots/version-catalog-update-plugin)
- [README](https://github.com/littlerobots/version-catalog-update-plugin/blob/main/README.md)
- [Releases](https://github.com/littlerobots/version-catalog-update-plugin/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/littlerobots-version-catalog-update-plugin
