# Version Catalogs

version 9.6.0-20260617124657+0000

A version catalog is a selected list of dependencies that can be referenced in build scripts, simplifying dependency management.

Instead of specifying dependencies directly using string notation, you can pick them from a version catalog:

build.gradle.kts

    dependencies {
        implementation(libs.groovy.core)
    }

build.gradle

    dependencies {
        implementation(libs.groovy.core)
    }

In this example, `libs` represents the catalog, and `groovy` is a dependency available in it.

Where the version catalog defining `libs.groovy.core` is a `libs.versions.toml` file in the `gradle` directory:

gradle/libs.versions.toml

    [libraries]
    groovy-core = { group = "org.codehaus.groovy", name = "groovy", version = "3.0.5" }

Version catalogs offer several advantages:

- Type-Safe Accessors: Gradle generates type-safe accessors for each catalog, enabling autocompletion in IDEs.

- Centralized Version Management: Each catalog is visible to all projects in a build.

- Dependency Bundles: Catalogs can group commonly used dependencies into bundles.

- Version Separation: Catalogs can separate dependency coordinates from version information, allowing shared version declarations.

- Conflict Resolution: Like regular dependency notation, version catalogs declare requested versions but do not enforce them during conflict resolution.

| Version catalogs declare requested versions but do not enforce them. Gradle may still select different versions due to dependency graph conflicts or constraints applied through platforms or other dependency management APIs.

## Using a catalog

To access items in a version catalog defined in the standard `libs.versions.toml` file located in the `gradle` directory, you use the `libs` object in your build scripts. For example, to reference a library, you can use `libs.<alias>`, and for a plugin, you can use `libs.plugins.<alias>`.

Declaring dependencies using a version catalog:

build.gradle.kts

    dependencies {
        implementation(libs.groovy.core)
        implementation(libs.groovy.json)
        implementation(libs.groovy.nio)
    }

build.gradle

    dependencies {
        implementation libs.groovy.core
        implementation libs.groovy.json
        implementation libs.groovy.nio
    }

Is the same as:

build.gradle.kts

    dependencies {
        implementation("org.codehaus.groovy:groovy:3.0.5")
        implementation("org.codehaus.groovy:groovy-json:3.0.5")
        implementation("org.codehaus.groovy:groovy-nio:3.0.5")
    }

build.gradle

    dependencies {
        implementation 'org.codehaus.groovy:groovy:3.0.5'
        implementation 'org.codehaus.groovy:groovy-json:3.0.5'
        implementation 'org.codehaus.groovy:groovy-nio:3.0.5'
    }

If the catalog looks as follows:

gradle/libs.versions.toml

    [versions]
    groovy = "3.0.5"

    [libraries]
    groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
    groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" }
    groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" }

Where the catalog can be imported from a local file or a repository.

Version catalogs use a TOML format in which accessors map directly to the aliases and versions defined in it, providing type-safe access to dependencies and plugins. This enables IDEs to provide autocompletion, highlight typos, and identify missing dependencies as errors.

### Aliases and type-safe accessors

Aliases in a version catalog consist of identifiers separated by a dash (`-`) or underscore (`_`). Type-safe accessors are generated for each alias, normalized to dot notation.

Consider the following catalog:

libs.versions.toml

    [libraries]
    # Dashes create nested accessor groups
    ktor-client-core    = "io.ktor:ktor-client-core:3.1.0"
    ktor-client-cio     = "io.ktor:ktor-client-cio:3.1.0"
    ktor-client-logging = "io.ktor:ktor-client-logging:3.1.0"

    # A single-segment alias stays flat
    junit               = "junit:junit:4.13.2"

Each dash in the alias maps to a dot in the accessor, creating a nested group that IDEs can autocomplete:

Alias | Generated accessor
--- | ---
`ktor-client-core` | `libs.ktor.client.core`
`ktor-client-cio` | `libs.ktor.client.cio`
`ktor-client-logging` | `libs.ktor.client.logging`
`junit` | `libs.junit`

### Classifiers, artifact types, and capabilities

Version catalogs intentionally capture only dependency coordinates: `group`, `name`, and `version`. They do not support classifiers, artifact types (`@aar`, `@zip`), excludes, or capabilities directly in the TOML file.

libs.versions.toml

    [libraries]
    my-lib = { module = "com.example:my-lib", version = "1.0.0" }

When you need a specific classifier for a dependency declared in a version catalog, use `variantOf()` at the dependency declaration site in your build script:

build.gradle.kts

    dependencies {
        implementation(variantOf(libs.my.lib) { classifier("test-fixtures") })
    }

build.gradle

    dependencies {
        implementation(variantOf(libs.my.lib) { classifier('test-fixtures') })
    }

This keeps the catalog focused on coordinates while letting each consumer choose the appropriate classifier.

For artifact types (common in Android projects needing `@aar`), use the `artifact` block:

build.gradle.kts

    dependencies {
        implementation(libs.my.lib) {
            artifact {
                name = "my-lib"
                type = "aar"
            }
        }
    }

build.gradle

    dependencies {
        implementation(libs.my.lib) {
            artifact {
                name = 'my-lib'
                type = 'aar'
            }
        }
    }

See TOML Limitations to learn more.

## Importing a published catalog

A catalog published with the `version-catalog` plugin can be imported in a consumer’s `settings.gradle(.kts)` file using the Settings API:

settings.gradle.kts

    dependencyResolutionManagement {
        versionCatalogs {
            create("libs") {
                from("com.mycompany:catalog:1.0")
            }
        }
    }

settings.gradle

    dependencyResolutionManagement {
        versionCatalogs {
            libs {
                from("com.mycompany:catalog:1.0")
            }
        }
    }

The `from()` method accepts standard dependency notation (`"group:artifact:version"`). Gradle downloads the TOML file from whatever repositories are configured in the `dependencyResolutionManagement` block and makes its entries available as type-safe accessors.

Once imported, all projects in the build can reference the catalog’s libraries, plugins, and bundles using the catalog name (e.g., `libs.someLibrary`).

## Importing a catalog from a file

| Gradle automatically imports a catalog in the `gradle` directory named `libs.versions.toml`. You do not need to import it programmatically.

The version catalog builder API allows importing a catalog from an external file, enabling reuse across different parts of a build, such as sharing the main build’s catalog with `buildSrc`.

For example, you can include a catalog in the `buildSrc/settings.gradle(.kts)` file as follows:

settings.gradle.kts

    dependencyResolutionManagement {
        versionCatalogs {
            create("libs") {
                from(files("../gradle/libs.versions.toml"))
            }
        }
    }

settings.gradle

    dependencyResolutionManagement {
        versionCatalogs {
            libs {
                from(files("../gradle/libs.versions.toml"))
            }
        }
    }

The VersionCatalogBuilder.from(Object dependencyNotation) method accepts only a single file, meaning that notations like Project.files(java.lang.Object…​) must refer to one file. Otherwise, the build will fail.

| Remember that you don’t need to import the version catalog named `libs.versions.toml` if it resides in your `gradle` folder. It will be imported automatically.

## Importing multiple catalogs

You can declare multiple catalogs using the Settings API.

For example, the default `libs` catalog is auto-imported from `gradle/libs.versions.toml`. To add additional catalogs for build tools and test dependencies, create `gradle/tools.versions.toml` and `gradle/test.versions.toml` files and register them:

settings.gradle.kts

    versionCatalogs {
        create("tools") {
            from(files("gradle/tools.versions.toml"))
        }
        create("testLibs") {
            from(files("gradle/test.versions.toml"))
        }
        // HOWEVER THIS IS NOT ALLOWED - IT WILL NOT WORK
        /* In version catalog libs, you can only call the 'from' method a single time:
        create("libs") {
            from(files("gradle/base.versions.toml"))
            from(files("gradle/extras.versions.toml")) // Error!
        }
        */

    }

settings.gradle

    versionCatalogs {
        tools {
            from(files("gradle/tools.versions.toml"))
        }
        testLibs {
            from(files("gradle/test.versions.toml"))
        }
        // HOWEVER THIS IS NOT ALLOWED - IT WILL NOT WORK
        /* In version catalog libs, you can only call the 'from' method a single time:
        libs {
            from(files("gradle/base.versions.toml"))
            from(files("gradle/extras.versions.toml")) // Error!
        }
        */
    }

Each catalog generates its own extension, so you reference them by name in build scripts:

build.gradle.kts

    dependencies {
        implementation(libs.guava)
        implementation(libs.commons.lang3)

        implementation(tools.errorprone.annotations)
        implementation(tools.jsr305)

        testImplementation(testLibs.junit.api)
        testImplementation(testLibs.mockito)
    }

build.gradle

    dependencies {
        implementation libs.guava
        implementation libs.commons.lang3

        implementation tools.errorprone.annotations
        implementation tools.jsr305

        testImplementation testLibs.junit.api
        testImplementation testLibs.mockito
    }

| To minimize the risk of naming conflicts, each catalog generates an extension applied to all projects, so it’s advisable to choose a unique name. One effective approach is to select a name that ends with `Libs`.

## Creating a catalog

Version catalogs are conventionally declared using a `libs.versions.toml` file located in the `gradle` subdirectory of the root build. They use the TOML format:

gradle/libs.versions.toml

    [versions]
    groovy = "3.0.5"
    checkstyle = "8.37"

    [libraries]
    groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
    groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" }
    groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" }
    commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer = "3.9" } }

    [bundles]
    groovy = ["groovy-core", "groovy-json", "groovy-nio"]

    [plugins]
    versions = { id = "com.github.ben-manes.versions", version = "0.45.0" }

However, they can reside in other folders and use different names. See Importing a catalog from a file for importing a catalog from a custom location, or Importing multiple catalogs for registering additional catalogs.

### The TOML format

The version catalog TOML file has four sections:

- `[versions]` – Declares version identifiers.

- `[libraries]` – Maps aliases to GAV coordinates.

- `[bundles]` – Defines dependency bundles.

- `[plugins]` – Declares plugin versions.

The TOML file format is very lenient and allows you to write "dotted" properties as shortcuts for full object declarations.

#### Versions

Versions can be declared either as a single string, in which case they are interpreted as a required version, or as a rich version:

    [versions]
    other-lib = "5.5.0" # Required version
    my-lib = { strictly = "[1.0, 2.0[", prefer = "1.2" } # Rich version

Supported members of a version declaration are:

- `require`: the required version

- `strictly`: the strict version

- `prefer`: the preferred version

- `reject`: the list of rejected versions

- `rejectAll`: a boolean to reject all versions

#### Libraries

Each library is mapped to a GAV coordinate: `group`, `artifact`, `version`. They can be declared as a simple string, in which case they are interpreted as coordinates, or a separate group and name:

gradle/test-libs.versions.toml

    [versions]
    common = "1.4"

    [libraries]
    my-lib = "com.mycompany:mylib:1.4"
    my-lib-no-version.module = "com.mycompany:mylib"
    my-other-lib = { module = "com.mycompany:other", version = "1.4" }
    my-other-lib2 = { group = "com.mycompany", name = "alternate", version = "1.4" }
    mylib-full-format = { group = "com.mycompany", name = "alternate", version = { require = "1.4" } }

You can also define strict or preferred versions using `strictly` or `prefer`:

    [libraries]
    commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer = "3.9" } }

In case you want to reference a version declared in the `[versions]` section, use the `version.ref` property:

    [versions]
    some = "1.4"

    [libraries]
    my-lib = { group = "com.mycompany", name="mylib", version.ref="some" }

#### Bundles

Bundles group multiple library aliases, so they can be referenced together in the build script.

    [versions]
    groovy = "3.0.9"

    [libraries]
    groovy-core = { group = "org.codehaus.groovy", name = "groovy", version.ref = "groovy" }
    groovy-json = { group = "org.codehaus.groovy", name = "groovy-json", version.ref = "groovy" }
    groovy-nio = { group = "org.codehaus.groovy", name = "groovy-nio", version.ref = "groovy" }

    [bundles]
    groovy = ["groovy-core", "groovy-json", "groovy-nio"]

This is useful for pulling in several related dependencies with a single alias:

build.gradle.kts

    dependencies {
        implementation(libs.bundles.groovy)
    }

build.gradle

    dependencies {
        implementation libs.bundles.groovy
    }

#### Plugins

This section defines the plugins and their versions by mapping plugin IDs to version numbers. Just like libraries, you can define plugin versions using aliases from the `[versions]` section or directly specify the version.

    [plugins]
    versions = { id = "com.github.ben-manes.versions", version = "0.45.0" }

Which can be accessed in any project of the build using the `plugins {}` block. To refer to a plugin from the catalog, use the `alias()` function:

build.gradle.kts

    plugins {
        `java-library`
        checkstyle
        alias(libs.plugins.versions)
    }

build.gradle

    plugins {
        id 'java-library'
        id 'checkstyle'
        // Use the plugin `versions` as declared in the `libs` version catalog
        alias(libs.plugins.versions)
    }

| You cannot use a plugin declared in a version catalog in your settings file or settings plugin.

### Avoiding subgroup accessors

To avoid generating subgroup accessors, use camelCase notation:

Aliases | Accessors
--- | ---
`groovyCore` | `libs.groovyCore`
`groovyJson-core` | `libs.groovyJson.core`

If you cannot rename aliases, for example, when consuming a published catalog, use `asProvider()` to access the parent version directly.

Given a catalog with both `jackson` and `jackson-databind` version aliases, `libs.versions.jackson` returns the subgroup accessor (because `jackson-databind` creates a `jackson.databind` subgroup). To get the `jackson` version itself, call `asProvider()`:

build.gradle.kts

    val jacksonVersion = libs.versions.jackson.asProvider()

build.gradle

    def jacksonVersion = libs.versions.jackson.asProvider()

### Reserved keywords

Certain keywords, like `extensions`, `class`, and `convention`, are reserved and cannot be used as aliases. Additionally, `bundles`, `versions`, and `plugins` cannot be the first subgroup in a dependency alias.

For example, the alias `versions-dependency` is not valid, but `versionsDependency` or `dependency-versions` are valid.

### Limitations

These are explicit limitations the catalog TOML format does not support:

- `classifier` attributes

- `type = "aar"` or `ext = "zip"` artifact types

- `exclude` rules

- `capabilities` requirements

## Programming catalogs

Version catalogs can be declared programmatically in the `settings.gradle(.kts)` file using the Settings API:

settings.gradle.kts

    dependencyResolutionManagement {
        versionCatalogs {
            create("libs") {
                version("groovy", "3.0.5")
                version("checkstyle", "8.37")
                library("groovy-core", "org.codehaus.groovy", "groovy").versionRef("groovy")
                library("groovy-json", "org.codehaus.groovy", "groovy-json").versionRef("groovy")
                library("groovy-nio", "org.codehaus.groovy", "groovy-nio").versionRef("groovy")
                library("commons-lang3", "org.apache.commons", "commons-lang3").version {
                    strictly("[3.8, 4.0[")
                    prefer("3.9")
                }
            }
        }
    }

settings.gradle

    dependencyResolutionManagement {
        versionCatalogs {
            libs {
                version('groovy', '3.0.5')
                version('checkstyle', '8.37')
                library('groovy-core', 'org.codehaus.groovy', 'groovy').versionRef('groovy')
                library('groovy-json', 'org.codehaus.groovy', 'groovy-json').versionRef('groovy')
                library('groovy-nio', 'org.codehaus.groovy', 'groovy-nio').versionRef('groovy')
                library('commons-lang3', 'org.apache.commons', 'commons-lang3').version {
                    strictly '[3.8, 4.0['
                    prefer '3.9'
                }
            }
        }
    }

| Don’t use `libs` for your programmatic version catalog name if you have the default `libs.versions.toml` in your project.

## Publishing a catalog

There are three ways to share a version catalog across builds:

1. Check the TOML file into source control — The simplest approach. Place `gradle/libs.versions.toml` in a shared repository that all builds can access. This works well for monorepos or when teams can reference a common Git repository.

2. Write a settings plugin — Create a plugin that programmatically declares catalog entries, publish it to the Gradle Plugin Portal or an internal repository, and have consumers apply it in their settings file. This offers the most flexibility but requires more effort.

3. Use the `version-catalog` plugin — Publish the catalog as a Maven or Ivy artifact so it can be consumed like any other dependency. This is the best approach for organizations that need to distribute a catalog to many independent builds.

### Using the `version-catalog` plugin

The `version-catalog` plugin lets you publish a catalog as a Maven or Ivy artifact. It works with both an existing TOML file and programmatic catalog declarations.

If you already have a `gradle/libs.versions.toml` file, you can publish it directly by importing it into the catalog extension:

build.gradle.kts

    plugins {
        `version-catalog`
        `maven-publish`
    }

    catalog {
        versionCatalog {
            from(files("gradle/libs.versions.toml"))
        }
    }

    group = "com.mycompany"
    version = "1.0"

    publishing {
        publications {
            create<MavenPublication>("maven") {
                from(components["versionCatalog"])
            }
        }
    }

build.gradle

    plugins {
        id 'version-catalog'
        id 'maven-publish'
    }

    catalog {
        versionCatalog {
            from(files("gradle/libs.versions.toml"))
        }
    }

    group = 'com.mycompany'
    version = '1.0'

    publishing {
        publications {
            maven(MavenPublication) {
                from components.versionCatalog
            }
        }
    }

This keeps the TOML file as the single source of truth — there is no need to redeclare entries in the build script.

Alternatively, you can declare the catalog entries programmatically in the `catalog` extension:

build.gradle.kts

    catalog {
        // declare the aliases, bundles and versions in this block
        versionCatalog {
            library("my-lib", "com.mycompany:mylib:1.2")
        }
    }

build.gradle

    catalog {
        // declare the aliases, bundles and versions in this block
        versionCatalog {
            library('my-lib', 'com.mycompany:mylib:1.2')
        }
    }

In both cases, configure a publication using the `versionCatalog` component:

build.gradle.kts

    publishing {
        publications {
            create<MavenPublication>("maven") {
                from(components["versionCatalog"])
            }
        }
    }

build.gradle

    publishing {
        publications {
            maven(MavenPublication) {
                from components.versionCatalog
            }
        }
    }

When you run `./gradlew publish`, the catalog is uploaded as a Maven artifact (with the `.toml` extension). This artifact can then be imported by other Gradle builds.

| The published artifact is a standard TOML file attached to Maven coordinates (e.g., `com.mycompany:catalog:1.0`). Consumers reference these coordinates to import the catalog — they do not need access to the original source code or build script.

For example, a consumer imports the published catalog in their `settings.gradle(.kts)` file:

settings.gradle.kts

    dependencyResolutionManagement {
        versionCatalogs {
            create("libs") {
                from("com.mycompany:catalog:1.0")
            }
        }
    }

settings.gradle

    dependencyResolutionManagement {
        versionCatalogs {
            libs {
                from("com.mycompany:catalog:1.0")
            }
        }
    }

## Changing the catalog name

By default, the `libs.versions.toml` file is used as input for the `libs` catalog. However, you can rename the default catalog if an extension with the same name already exists:

settings.gradle.kts

    dependencyResolutionManagement {
        defaultLibrariesExtensionName = "projectLibs"
    }

settings.gradle

    dependencyResolutionManagement {
        defaultLibrariesExtensionName = 'projectLibs'
    }

## Overwriting catalog versions

You can overwrite versions when importing a catalog:

settings.gradle.kts

    dependencyResolutionManagement {
        versionCatalogs {
            create("amendedLibs") {
                from("com.mycompany:catalog:1.0")
                // overwrite the "groovy" version declared in the imported catalog
                version("groovy", "3.0.6")
            }
        }
    }

settings.gradle

    dependencyResolutionManagement {
        versionCatalogs {
            amendedLibs {
                from("com.mycompany:catalog:1.0")
                // overwrite the "groovy" version declared in the imported catalog
                version("groovy", "3.0.6")
            }
        }
    }

In the examples above, any dependency referencing the `groovy` version will automatically be updated to use `3.0.6`. You can see how this is used in Working with multiple version catalogs.

| Overwriting a version only affects what is imported and used when declaring dependencies. The actual resolved dependency version may differ due to conflict resolution.

## Using a catalog in `buildSrc`

Version catalogs provide a centralized way to manage dependencies in a project. However, `buildSrc` does not automatically inherit the version catalog from the main project, so additional configuration is required.

To access a version catalog inside `buildSrc`, you need to explicitly import it in `buildSrc/settings.gradle(.kts)`:

buildSrc/settings.gradle.kts

    // Add the version catalog to buildSrc using dependencyResolutionManagement
    dependencyResolutionManagement {
        versionCatalogs {
            create("libs") {
                from(files("../gradle/libs.versions.toml"))
            }
        }
    }

buildSrc/settings.gradle

    // Add the version catalog to buildSrc using dependencyResolutionManagement
    dependencyResolutionManagement {
        versionCatalogs {
            create("libs") {
                from(files("../gradle/libs.versions.toml"))
            }
        }
    }

Once the version catalog is imported, dependencies can be referenced using a few tricks:

buildSrc/build.gradle.kts

    plugins {
        `kotlin-dsl`
        alias(libs.plugins.versions) // Access version catalog in buildSrc build file for plugin
    }

    repositories {
        gradlePluginPortal()
    }

    dependencies {
        // Access version catalog in buildSrc build file for dependencies
        implementation(plugin(libs.plugins.jacocolog)) // Plugin dependency
        implementation(libs.groovy.core) // Regular library from version catalog
        implementation("org.apache.commons:commons-lang3:3.9") // Direct dependency
    }

    // Helper function that transforms a Gradle Plugin alias from a
    // Version Catalog into a valid dependency notation for buildSrc
    fun DependencyHandlerScope.plugin(plugin: Provider<PluginDependency>) =
        plugin.map { "${it.pluginId}:${it.pluginId}.gradle.plugin:${it.version}" }

buildSrc/build.gradle

    plugins {
        id 'groovy-gradle-plugin'
        alias(libs.plugins.versions) // Access version catalog in buildSrc for plugin
    }

    repositories {
        gradlePluginPortal()
    }

    def catalogs = project.extensions.getByType(VersionCatalogsExtension)
    def libs = catalogs.named("libs")

    dependencies {
        // Access version catalog in buildSrc for dependencies
        implementation plugin(libs.findPlugin("jacocolog").get()) // Plugin dependency
        implementation libs.findLibrary("groovy-core").get() // Regular library from version catalog
        implementation "org.apache.commons:commons-lang3:3.9" // Direct dependency
    }

    // Helper function that transforms a Gradle Plugin alias from a
    // Version Catalog into a valid dependency notation for buildSrc
    def plugin(Provider<PluginDependency> plugin) {
        return plugin.map { it.pluginId + ":" + it.pluginId + ".gradle.plugin:" + it.version }
    }

In precompiled script plugins inside `buildSrc`, the version catalog can be accessed using `extensions.getByType(VersionCatalogsExtension)` as demonstrated in the dependencies block of this convention plugin:

buildSrc/src/main/kotlin/java-commons-convention.gradle.kts

    plugins {
        id("java-library")

        //alias(libs.plugins.jacocolog) // Unfortunately it is not possible the version catalog in buildSrc code for plugins

        // Remember that unlike regular Gradle projects, convention plugins in buildSrc do not automatically resolve
        // external plugins. We must declare them as dependencies in buildSrc/build.gradle.kts.
        id("org.barfuin.gradle.jacocolog") // Apply the plugin manually as a workaround with the external plugin
                                           // version from the version catalog specified in implementation dependency
                                           // artifact in build file
    }

    repositories {
        mavenCentral()
    }

    // Access the version catalog
    val libs = extensions.getByType(VersionCatalogsExtension::class.java).named("libs")
    //val libs = the<VersionCatalogsExtension>().named("libs")

    dependencies {
        // Access version catalog in buildSrc code for dependencies
        implementation(libs.findLibrary("guava").get()) // Regular library from version catalog
        testImplementation(platform("org.junit:junit-bom:5.9.1")) // Platform dependency
        testImplementation("org.junit.jupiter:junit-jupiter") // Direct dependency
    }

    tasks.test {
        useJUnitPlatform()
    }

buildSrc/src/main/groovy/java-commons-convention.gradle

    plugins {
        id 'java-library'

        // alias(libs.plugins.jacocolog) - Unfortunately, it is not possible to use the version catalog for plugins in buildSrc

        // Unlike regular Gradle projects, convention plugins in buildSrc do not automatically resolve
        // external plugins. We must declare them as dependencies in buildSrc/build.gradle.
        id 'org.barfuin.gradle.jacocolog' // Apply the plugin manually as a workaround
        // The external plugin version comes from the implementation dependency
        // artifact in the build file
    }

    repositories {
        mavenCentral()
    }

    // Access the version catalog
    def libs = project.extensions.getByType(VersionCatalogsExtension).named("libs")

    dependencies {
        // Access version catalog in buildSrc for dependencies
        implementation libs.findLibrary("guava").get() // Regular library from version catalog
        testImplementation platform("org.junit:junit-bom:5.9.1") // Platform dependency
        testImplementation "org.junit.jupiter:junit-jupiter" // Direct dependency
    }

    tasks.withType(Test).configureEach {
        useJUnitPlatform()
    }

However, the plugins block in the precompiled script plugin cannot access the version catalog.

## Working with multiple version catalogs

Most builds should use a single `libs.versions.toml` file. A single catalog centralizes all dependency coordinates, avoids version duplication, and lets every subproject share the same type-safe accessors. Use naming conventions, such as alias prefixes, to logically separate groups of dependencies within that file:

libs.versions.toml

    [versions]
    kotlin = "2.3.21"
    junit = "5.12.0"

    [libraries]
    # ── Production ───────────────────────────────
    guava = { module = "com.google.guava:guava", version = "33.4.0-jre" }
    commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.17.0" }

    # ── Testing ──────────────────────────────────
    junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
    mockito-core = { module = "org.mockito:mockito-core", version = "5.14.0" }

Consider splitting into multiple catalogs only when you have a concrete need to enforce boundaries between dependency scopes. Typical reasons include:

- Auditing shipped artifacts. When you must clearly distinguish which dependencies end up in a published distribution from those used only for testing or code generation, separate catalogs make accidental cross-contamination visible at the declaration site.

- Independent build-logic dependencies. Plugins applied inside `build-logic` or `buildSrc` may need their own catalog that is resolved in the settings classpath — before the main build’s catalog is available.

- Organizational sharing. An organization-wide "platform" catalog is published as a Maven artifact and imported by many repositories, while each repository maintains a local catalog for project-specific dependencies.

If none of these situations apply, a single catalog with clear naming conventions is simpler and avoids the version-sharing challenges described below.

### Sharing Versions Across Multiple Catalogs

Each version catalog TOML file is self-contained. There is no built-in syntax, such as an `include` directive or cross-file `version.ref`, that lets one TOML file reference a version declared in another. If the same library version must appear in two catalogs, you must arrange for it to reach both catalogs yourself.

The `from()` method, which loads a catalog from a TOML file or a published artifact, may only be called once per catalog. Attempting to call it twice produces an error:

| In version catalog libs, you can only call the `from` method a single time.

This means you cannot merge two TOML files into a single catalog.

Below are three concrete workarounds, ordered from simplest to most flexible.

#### Workaround 1 — Duplicate and verify

Declare the shared version in every TOML file that needs it, and add a lightweight check that the values stay in sync:

distribution.versions.toml

    [versions]
    errorProne = "2.28.0"

test.versions.toml

    [versions]
    errorProne = "2.28.0"

Add a small verification task (or a CI script) that parses both files and fails the build if the values diverge:

build.gradle.kts

    tasks.register("verifySharedVersions") {
        val distToml = file("gradle/distribution.versions.toml").readText()
        val testToml = file("gradle/test.versions.toml").readText()

        doLast {
            // Extract the errorProne version from each file
            val regex = """errorProne\s*=\s*"(.+?)"""".toRegex()
            val distVersion = regex.find(distToml)?.groupValues?.get(1)
            val testVersion = regex.find(testToml)?.groupValues?.get(1)

            require(distVersion == testVersion) {
                "errorProne version mismatch: distribution=$distVersion, test=$testVersion"
            }
        }
    }

build.gradle

    tasks.register('verifySharedVersions') {
        def distToml = file('gradle/distribution.versions.toml').text
        def testToml = file('gradle/test.versions.toml').text

        doLast {
            // Extract the errorProne version from each file
            def regex = /errorProne\s*=\s*"(.+?)"/
            def distMatcher = distToml =~ regex
            def testMatcher = testToml =~ regex

            def distVersion = distMatcher ? distMatcher[0][1] : null
            def testVersion = testMatcher ? testMatcher[0][1] : null

            assert distVersion == testVersion :
                "errorProne version mismatch: distribution=$distVersion, test=$testVersion"
        }
    }

This approach is straightforward and keeps each TOML file independently readable, but it requires discipline and will not scale well beyond a handful of shared versions.

#### Workaround 2 — External properties file injected programmatically

Extract shared versions into a plain properties file and inject them into each catalog in `settings.gradle(.kts)`.

shared-versions.properties

    errorProne=2.28.0
    kotlin=2.3.21

settings.gradle.kts

    val sharedVersions = java.util.Properties().apply {
        file("gradle/shared-versions.properties").inputStream().use { load(it) }
    }

    dependencyResolutionManagement {
        repositories {
            mavenCentral()
        }
        versionCatalogs {
            create("distributionLibs") {
                from(files("gradle/distribution.versions.toml"))
                version("errorProne", sharedVersions.getProperty("errorProne"))
                version("kotlin", sharedVersions.getProperty("kotlin"))
            }
            create("testLibs") {
                from(files("gradle/test.versions.toml"))
                version("errorProne", sharedVersions.getProperty("errorProne"))
                version("kotlin", sharedVersions.getProperty("kotlin"))
            }
        }
    }

settings.gradle

    def sharedVersions = new Properties()
    file('gradle/shared-versions.properties').withInputStream { sharedVersions.load(it) }

    dependencyResolutionManagement {
        repositories {
            mavenCentral()
        }
        versionCatalogs {
            distributionLibs {
                from(files('gradle/distribution.versions.toml'))
                version('errorProne', sharedVersions.getProperty('errorProne'))
                version('kotlin', sharedVersions.getProperty('kotlin'))
            }
            testLibs {
                from(files('gradle/test.versions.toml'))
                version('errorProne', sharedVersions.getProperty('errorProne'))
                version('kotlin', sharedVersions.getProperty('kotlin'))
            }
        }
    }

The TOML files use `version.ref` to reference these versions as if they were declared locally:

distribution.versions.toml

    # The "errorProne" and "kotlin" versions are injected
    # programmatically in settings.gradle.kts — do not
    # declare them here.

    [libraries]
    errorProne-core = { module = "com.google.errorprone:error_prone_core", version.ref = "errorProne" }
    kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }

When you call `version("errorProne", "2.28.0")` on the catalog builder after `from(files(…​))`, the programmatic call adds or overrides the version in the catalog that was loaded from the TOML file. Libraries in the TOML file that reference `version.ref = "errorProne"` will pick up the injected value.

This is the pattern the Gradle Build Tool team uses in the `gradle/gradle` repository, where a `shared-versions.properties` file feeds versions into four separate catalogs (`distribution`, `provided`, `test`, `build`).

#### Workaround 3 — Published base catalog with local overrides

For organizations that share a common set of dependency versions across many repositories, publish a base catalog as a Maven artifact and let each repository layer project-specific versions on top.

Publishing project (`build.gradle(.kts)`):

build.gradle.kts

    plugins {
        `version-catalog`
        `maven-publish`
    }

    catalog {
        versionCatalog {
            version("kotlin", "2.3.21")
            version("errorProne", "2.28.0")
            library("kotlin-stdlib", "org.jetbrains.kotlin", "kotlin-stdlib").versionRef("kotlin")
            library("errorProne-core", "com.google.errorprone", "error_prone_core").versionRef("errorProne")
        }
    }

    group = "com.mycompany"
    version = "1.0"

    publishing {
        publications {
            create<MavenPublication>("catalog") {
                from(components["versionCatalog"])
            }
        }
    }

build.gradle

    plugins {
        id 'version-catalog'
        id 'maven-publish'
    }

    catalog {
        versionCatalog {
            version('kotlin', '2.3.21')
            version('errorProne', '2.28.0')
            library('kotlin-stdlib', 'org.jetbrains.kotlin', 'kotlin-stdlib').versionRef('kotlin')
            library('errorProne-core', 'com.google.errorprone', 'error_prone_core').versionRef('errorProne')
        }
    }

    group = 'com.mycompany'
    version = '1.0'

    publishing {
        publications {
            maven(MavenPublication) {
                from components.versionCatalog
            }
        }
    }

Consuming project (`settings.gradle(.kts)`):

settings.gradle.kts

    dependencyResolutionManagement {
        versionCatalogs {
            // Import the organization-wide catalog
            create("libs") {
                from("com.mycompany:catalog:1.0")
                // Override a specific version for this project
                version("kotlin", "2.3.21")
            }

            // A local catalog for project-specific dependencies
            create("projectLibs") {
                from(files("gradle/project.versions.toml"))
            }
        }
    }

settings.gradle

    dependencyResolutionManagement {
        versionCatalogs {
            // Import the organization-wide catalog
            libs {
                from('com.mycompany:catalog:1.0')
                // Override a specific version for this project
                version('kotlin', '2.3.21')
            }

            // A local catalog for project-specific dependencies
            projectLibs {
                from(files('gradle/project.versions.toml'))
            }
        }
    }

This pattern cleanly separates organizational standards from project-level concerns, but it introduces a publishing and versioning lifecycle for the catalog artifact itself.

### Combining `from()` with Programmatic `version()` Calls

The version catalog builder API allows you to mix a TOML file import with programmatic declarations in the same catalog. The `from()` call loads the base content, and subsequent `version()`, `library()`, `bundle()`, or `plugin()` calls add to or override that content.

settings.gradle.kts

    dependencyResolutionManagement {
        versionCatalogs {
            create("libs") {
                from(files("gradle/catalog.versions.toml"))

                // Override an existing version
                version("groovy", "3.0.6")

                // Add a new version not present in the TOML file
                version("errorProne", "2.28.0")

                // Add a library that uses the injected version
                library("errorProne-core", "com.google.errorprone", "error_prone_core").versionRef("errorProne")
            }
        }
    }

settings.gradle

    dependencyResolutionManagement {
        versionCatalogs {
            libs {
                from(files("gradle/catalog.versions.toml"))

                // Override an existing version
                version("groovy", "3.0.6")

                // Add a new version not present in the TOML file
                version("errorProne", "2.28.0")

                // Add a library that uses the injected version
                library("errorProne-core", "com.google.errorprone", "error_prone_core").versionRef("errorProne")
            }
        }
    }

This works identically when importing from a published artifact:

    create("libs") {
        from("com.mycompany:catalog:1.0")
        version("guava", "33.5.0-jre")  // override the published version
    }

    libs {
        from("com.mycompany:catalog:1.0")
        version("guava", "33.5.0-jre")  // override the published version
    }

A common scenario is needing the same Kotlin version for both the `kotlin("jvm")` plugin and the `kotlin-stdlib` library dependency. Plugins are resolved from the `[plugins]` section, while libraries come from `[libraries]` — and both need the same version.

gradle/catalog.versions.toml

    [libraries]
    kotlin-stdlib  = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
    kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }

    [plugins]
    kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

settings.gradle.kts

    dependencyResolutionManagement {
        versionCatalogs {
            create("libs") {
                from(files("gradle/catalog.versions.toml"))
                // Single source of truth for the Kotlin version
                version("kotlin", "2.3.21")
            }
        }
    }

settings.gradle

    dependencyResolutionManagement {
        versionCatalogs {
            libs {
                from(files("gradle/catalog.versions.toml"))
                // Single source of truth for the Kotlin version
                version("kotlin", "2.3.21")
            }
        }
    }

Both the plugin alias and the library aliases resolve to `2.3.21`, and updating requires changing only one line in `settings.gradle(.kts)`.

## Using catalog entries in Gradle APIs

A version catalog accessor such as `libs.groovy.core` returns a `Provider<MinimalExternalModuleDependency>`. Calling `.get()` on it unwraps the provider and gives you the `MinimalExternalModuleDependency` directly.

In dependency declarations, both forms work identically:

build.gradle.kts

    dependencies {
        // Provider<MinimalExternalModuleDependency>
        implementation(libs.groovy.core)
        // MinimalExternalModuleDependency
        implementation(libs.groovy.core.get())
    }

build.gradle

    dependencies {
        // Provider<MinimalExternalModuleDependency>
        implementation(libs.groovy.core)
        // MinimalExternalModuleDependency
        implementation(libs.groovy.core.get())
    }

However, in other Gradle APIs, such as dependency substitution rules, dependency constraints, or any API that accepts a dependency notation, try to pass the provider (`libs.groovy.core`) rather than the unwrapped value (`libs.groovy.core.get()`):

build.gradle.kts

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            all {
                val componentSelector = requested
                if (componentSelector is ModuleComponentSelector
                    && componentSelector.group == "org.codehaus.groovy"
                    && componentSelector.module == "groovy-all") {
                    useTarget(libs.groovy.core)
                }
            }
        }
    }

build.gradle

    configurations.configureEach {
        resolutionStrategy.dependencySubstitution {
            all {
                if (requested instanceof ModuleComponentSelector
                    && requested.group == "org.codehaus.groovy"
                    && requested.module == "groovy-all") {
                    useTarget(libs.groovy.core)
                }
            }
        }
    }

When you pass the provider, Gradle handles any necessary type conversions internally. Unwrapping with `.get()` gives you a `MinimalExternalModuleDependency`, which implements `ExternalDependency` and `ModuleVersionSelector` — but some resolution APIs expect `ModuleComponentSelector`, a different type. Passing the provider avoids mismatches between these types.