# Sharing dependency versions between projects

version 7.3.3

## Central declaration of dependencies

| Central declaration of dependencies is an incubating feature. It requires the activation of the `VERSION_CATALOGS` feature preview.

### Using a version catalog

A version catalog is a list of dependencies, represented as dependency coordinates, that a user can pick from when declaring dependencies in a build script.

For example, instead of declaring a dependency using a string notation, the dependency coordinates can be picked from a version catalog:

Example 1. Using a library declared in a version catalog
build.gradle

    dependencies {
        implementation(libs.groovy.core)
    }

build.gradle.kts

    dependencies {
        implementation(libs.groovy.core)
    }

In this context, `libs` is a catalog and `groovy` represents a dependency available in this catalog. A version catalog provides a number of advantages over declaring the dependencies directly in build scripts:

- For each catalog, Gradle generates type-safe accessors so that you can easily add dependencies with autocompletion in the IDE.

- Each catalog is visible to all projects of a build. It is a central place to declare a version of a dependency and to make sure that a change to that version applies to every subproject.

- Catalogs can declare dependency bundles, which are "groups of dependencies" that are commonly used together.

- Catalogs can separate the group and name of a dependency from its actual version and use version references instead, making it possible to share a version declaration between multiple dependencies.

Adding a dependency using the `libs.someLib` notation works exactly like if you had hardcoded the group, artifact and version directly in the build script.

| A dependency catalog doesn’t enforce the version of a dependency: like a regular dependency notation, it declares the requested version or a rich version. That version is not necessarily the version that is selected during conflict resolution.

### Declaring a version catalog

Version catalogs can be declared in the `settings.gradle(.kts)` file. In the example above, in order to make `groovy` available via the `libs` catalog, we need to associate an alias with GAV (group, artifact, version) coordinates:

Example 2. Declaring a version catalog
settings.gradle

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

settings.gradle.kts

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

#### Aliases and their mapping to type safe accessors

Aliases must consist of a series of identifiers separated by a dash (`-`, recommended), an underscore (`_`) or a dot (`.`). Identifiers themselves must consist of ascii characters, preferably lowercase, eventually followed by numbers.

For example:

- `guava` is a valid alias

- `groovy-core` is a valid alias

- `commons-lang3` is a valid alias

- `androidx.awesome.lib` is also a valid alias

- but `this.#is.not!`

Then type safe accessors are generated for each subgroup. For example, given the following aliases in a version catalog named `libs`:

`guava`, `groovy-core`, `groovy-xml`, `groovy-json`, `androidx.awesome.lib`

We would generate the following type-safe accessors:

- `libs.guava`

- `libs.groovy.core`

- `libs.groovy.xml`

- `libs.groovy.json`

- `libs.androidx.awesome.lib`

Where the `libs` prefix comes from the version catalog name.

In case you want to avoid the generation of a subgroup accessor, we recommend relying on case to differentiate. For example the aliases `groovyCore`, `groovyJson` and `groovyXml` would be mapped to the `libs.groovyCore`, `libs.groovyJson` and `libs.groovyXml` accessors respectively.

When declaring aliases, it’s worth noting that any of the `-`, `_` and `.` characters can be used as separators, but the generated catalog will have all normalized to `.`: for example `foo-bar` as an alias is converted to `foo.bar` automatically.

| Some keywords are reserved, so they cannot be used as an alias. Next words cannot be used as an alias: extensions class convention Additional to that next words cannot be used as a first subgroup of an alias for dependencies (for bundles, versions and plugins this restriction doesn’t apply): bundles versions plugins So for example for dependencies an alias `versions-dependency` is not valid, but `versionsDependency` or `dependency-versions` are valid.

#### Dependencies with same version numbers

In the first example in declaring a version catalog, we can see that we declare 3 aliases for various components of the `groovy` library and that all of them share the same version number.

Instead of repeating the same version number, we can declare a version and reference it:

Example 3. Declaring versions separately from libraries
settings.gradle

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

settings.gradle.kts

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

Versions declared separately are also available via type-safe accessors, making them usable for more use cases than dependency versions, in particular for tooling:

Example 4. Using a version declared in a version catalog
build.gradle

    checkstyle {
        // will use the version declared in the catalog
        toolVersion = libs.versions.checkstyle.get()
    }

build.gradle.kts

    checkstyle {
        // will use the version declared in the catalog
        toolVersion = libs.versions.checkstyle.get()
    }

Dependencies declared in a catalog are exposed to build scripts via an extension corresponding to their name. In the example above, because the catalog declared in settings is named `libs`, the extension is available via the name `libs` in all build scripts of the current build. Declaring dependencies using the following notation…​

Example 5. Dependency notation correspondance
build.gradle

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

build.gradle.kts

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

…​has exactly the same effect as writing:

Example 6. Dependency notation correspondance
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'
    }

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")
    }

Versions declared in the catalog are rich versions. Please refer to the version catalog builder API for the full version declaration support documentation.

#### Dependency bundles

Because it’s frequent that some dependencies are systematically used together in different projects, a version catalog offers the concept of a "dependency bundle". A bundle is basically an alias for several dependencies. For example, instead of declaring 3 individual dependencies like above, you could write:

Example 7. Using a dependency bundle
build.gradle

    dependencies {
        implementation libs.bundles.groovy
    }

build.gradle.kts

    dependencies {
        implementation(libs.bundles.groovy)
    }

The bundle named `groovy` needs to be declared in the catalog:

Example 8. Declaring a dependency bundle
settings.gradle

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

settings.gradle.kts

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

The semantics are again equivalent: adding a single bundle is equivalent to adding all dependencies which are part of the bundle individually.

#### Plugins

In addition to libraries, version catalog supports declaring plugin versions. While libraries are represented by their group, artifact and version coordinates, Gradle plugins are identified by their id and version only. Therefore, they need to be declared separately:

Example 9. Declaring a plugin version
settings.gradle

    dependencyResolutionManagement {
        versionCatalogs {
            libs {
                alias('jmh').toPluginId('me.champeau.jmh').version('0.6.5')
            }
        }
    }

settings.gradle.kts

        dependencyResolutionManagement {
            versionCatalogs {
                create("libs") {
                    alias("jmh").toPluginId("me.champeau.jmh").version("0.6.5")
                }
            }
        }

Then the plugin is accessible in the `plugins` block and can be consumed in any project of the build using:

Example 10. Using a plugin declared in a catalog
build.gradle

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

build.gradle.kts

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

### The libs.versions.toml file

In addition to the settings API above, Gradle offers a conventional file to declare a catalog. If a `libs.versions.toml` file is found in the `gradle` subdirectory of the root build, then a catalog will be automatically declared with the contents of this file.

| Declaring a `libs.versions.toml` file doesn’t make it the single source of truth for dependencies: it’s a conventional location where dependencies can be declared. As soon as you start using catalogs, it’s strongly recommended to declare all your dependencies in a catalog and not hardcode group/artifact/version strings in build scripts. Be aware that it may happen that plugins add dependencies, which are dependencies defined outside of this file. Just like `src/main/java` is a convention to find the Java sources, which doesn’t prevent additional source directories to be declared (either in a build script or a plugin), the presence of the `libs.versions.toml` file doesn’t prevent the declaration of dependencies elsewhere. The presence of this file does, however, suggest that most dependencies, if not all, will be declared in this file. Therefore, updating a dependency version, for most users, should only consists of changing a line in this file.

By default, the `libs.versions.toml` file will be an input to the `libs` catalog. It is possible to change the name of the default catalog, for example if you already have an extension with the same name:

Example 11. Changing the default extension name
settings.gradle

    dependencyResolutionManagement {
        defaultLibrariesExtensionName.set('deps')
    }

settings.gradle.kts

    dependencyResolutionManagement {
        defaultLibrariesExtensionName.set("deps")
    }

#### The version catalog TOML file format

The TOML file consists of 4 major sections:

- the `[versions]` section is used to declare versions which can be referenced by dependencies

- the `[libraries]` section is used to declare the aliases to coordinates

- the `[bundles]` section is used to declare dependency bundles

- the `[plugins]` section is used to declare plugins

For example:

The libs.versions.toml file

    [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]
    jmh = { id = "me.champeau.jmh", version = "0.6.5" }

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

    [versions]
    my-lib = { strictly = "[1.0, 2.0[", prefer = "1.2" }

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

Dependency declaration can either be declared as a simple string, in which case they are interpreted as `group:artifact:version` coordinates, or separating the version declaration from the group and name:

| For aliases, the rules described in the section aliases and their mapping to type safe accessors apply as well.

Different dependency notations

    [versions]
    common = "1.4"

    [libraries]
    my-lib = "com.mycompany:mylib:1.4"
    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" } }

    [plugins]
    short-notation = "some.plugin.id:1.4"
    long-notation = { id = "some.plugin.id", version = "1.4" }
    reference-notation = { id = "some.plugin.id", version.ref = "common" }

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

    [versions]
    some = "1.4"

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

| The TOML file format is very lenient and lets you write "dotted" properties as shortcuts to full object declarations. For example, this: `a.b.c="d"` is equivalent to: `a.b = { c = "d" }` or `a = { b = { c = "d" } }` See the TOML specification for details.

### Type unsafe API

Version catalogs can be accessed through a type unsafe API. This API is available in situations where generated accessors are not. It is accessed through the version catalog extension:

build.gradle

    def versionCatalog = extensions.getByType(VersionCatalogsExtension).named("libs")
    println "Dependency aliases: ${versionCatalog.dependencyAliases}"
    dependencies {
        versionCatalog.findDependency("groovy-json").ifPresent {
            implementation(it)
        }
    }

build.gradle.kts

    val versionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")
    println("Dependency aliases: ${versionCatalog.dependencyAliases}")
    dependencies {
        versionCatalog.findDependency("groovy-json").ifPresent {
            implementation(it)
        }
    }

Check the version catalog API for all supported methods.

## Sharing catalogs

Version catalogs are used in a single build (possibly multi-project build) but may also be shared between builds. For example, an organization may want to create a catalog of dependencies that different projects, from different teams, may use.

### Importing a catalog from a TOML file

The version catalog builder API supports including a model from an external file. This makes it possible to reuse the catalog of the main build for `buildSrc`, if needed. For example, the `buildSrc/settings.gradle(.kts)` file can include this file using:

Example 12. Sharing the dependency catalog with buildSrc
settings.gradle

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

settings.gradle.kts

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

This technique can therefore be used to declare multiple catalogs from different files:

Example 13. Declaring additional catalogs
settings.gradle

    dependencyResolutionManagement {
        versionCatalogs {
            // declares an additional catalog, named 'testLibs', from the 'test-libs.versions.toml' file
            testLibs {
                from(files('gradle/test-libs.versions.toml'))
            }
        }
    }

settings.gradle.kts

    dependencyResolutionManagement {
        versionCatalogs {
            // declares an additional catalog, named 'testLibs', from the 'test-libs.versions.toml' file
            create("testLibs") {
                from(files("gradle/test-libs.versions.toml"))
            }
        }
    }

### The version catalog plugin

While importing catalogs from local files is convenient, it doesn’t solve the problem of sharing a catalog in an organization or for external consumers. One option to share a catalog is to write a settings plugin, publish it on the Gradle plugin portal or an internal repository, and let the consumers apply the plugin on their settings file.

Alternatively, Gradle offers a version catalog plugin, which offers the ability to declare, then publish a catalog.

To do this, you need to apply the `version-catalog` plugin:

Example 14. Applying the version catalog plugin
build.gradle

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

build.gradle.kts

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

This plugin will then expose the catalog extension that you can use to declare a catalog:

Example 15. Definition of a catalog
build.gradle

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

build.gradle.kts

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

Such a catalog can then be published by applying either the `maven-publish` or `ivy-publish` plugin and configuring the publication to use the `versionCatalog` component:

Example 16. Publishing a catalog
build.gradle

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

build.gradle.kts

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

When publishing such a project, a `libs.versions.toml` file will automatically be generated (and uploaded), which can then be consumed from other Gradle builds.

### Importing a published catalog

A catalog produced by the version catalog plugin can be imported via the settings API:

Example 17. Using a published catalog
settings.gradle

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

settings.gradle.kts

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

#### Overwriting catalog versions

In case a catalog declares a version, you can overwrite the version when importing the catalog:

Example 18. Overwriting versions declared in a published catalog
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")
            }
        }
    }

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")
                }
            }
        }

In the example above, any dependency which was using the `groovy` version as reference will be automatically updated to use `3.0.6`.

| Again, overwriting a version doesn’t mean that the actual resolved dependency version will be the same: this only changes what is imported, that is to say what is used when declaring a dependency. The actual version will be subject to traditional conflict resolution, if any.

## Using a platform to control transitive versions

A platform is a special software component which can be used to control transitive dependency versions. In most cases it’s exclusively composed of dependency constraints which will either suggest dependency versions or enforce some versions. As such, this is a perfect tool whenever you need to share dependency versions between projects. In this case, a project will typically be organized this way:

- a `platform` project which defines constraints for the various dependencies found in the different sub-projects

- a number of sub-projects which depend on the platform and declare dependencies without version

In the Java ecosystem, Gradle provides a plugin for this purpose.

It’s also common to find platforms published as Maven BOMs which Gradle supports natively.

A dependency on a platform is created using the `platform` keyword:

Example 19. Getting versions declared in a platform
build.gradle

    dependencies {
        // get recommended versions from the platform project
        api platform(project(':platform'))
        // no version required
        api 'commons-httpclient:commons-httpclient'
    }

build.gradle.kts

    dependencies {
        // get recommended versions from the platform project
        api(platform(project(":platform")))
        // no version required
        api("commons-httpclient:commons-httpclient")
    }

| This `platform` notation is a short-hand notation which actually performs several operations under the hood: it sets the org.gradle.category attribute to `platform`, which means that Gradle will select the platform component of the dependency. it sets the endorseStrictVersions behavior by default, meaning that if the platform declares strict dependencies, they will be enforced. This means that by default, a dependency to a platform triggers the inheritance of all strict versions defined in that platform, which can be useful for platform authors to make sure that all consumers respect their decisions in terms of versions of dependencies. This can be turned off by explicitly calling the `doNotEndorseStrictVersions` method.

## Importing Maven BOMs

Gradle provides support for importing bill of materials (BOM) files, which are effectively `.pom` files that use `<dependencyManagement>` to control the dependency versions of direct and transitive dependencies. The BOM support in Gradle works similar to using `<scope>import</scope>` when depending on a BOM in Maven. In Gradle however, it is done via a regular dependency declaration on the BOM:

Example 20. Depending on a BOM to import its dependency constraints
build.gradle

    dependencies {
        // import a BOM
        implementation platform('org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE')

        // define dependencies without versions
        implementation 'com.google.code.gson:gson'
        implementation 'dom4j:dom4j'
    }

build.gradle.kts

    dependencies {
        // import a BOM
        implementation(platform("org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE"))

        // define dependencies without versions
        implementation("com.google.code.gson:gson")
        implementation("dom4j:dom4j")
    }

In the example, the versions of `gson` and `dom4j` are provided by the Spring Boot BOM. This way, if you are developing for a platform like Spring Boot, you do not have to declare any versions yourself but can rely on the versions the platform provides.

Gradle treats all entries in the `<dependencyManagement>` block of a BOM similar to Gradle’s dependency constraints. This means that any version defined in the `<dependencyManagement>` block can impact the dependency resolution result. In order to qualify as a BOM, a `.pom` file needs to have `<packaging>pom</packaging>` set.

However often BOMs are not only providing versions as recommendations, but also a way to override any other version found in the graph. You can enable this behavior by using the `enforcedPlatform` keyword, instead of `platform`, when importing the BOM:

Example 21. Importing a BOM, making sure the versions it defines override any other version found
build.gradle

    dependencies {
        // import a BOM. The versions used in this file will override any other version found in the graph
        implementation enforcedPlatform('org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE')

        // define dependencies without versions
        implementation 'com.google.code.gson:gson'
        implementation 'dom4j:dom4j'

        // this version will be overridden by the one found in the BOM
        implementation 'org.codehaus.groovy:groovy:1.8.6'
    }

build.gradle.kts

    dependencies {
        // import a BOM. The versions used in this file will override any other version found in the graph
        implementation(enforcedPlatform("org.springframework.boot:spring-boot-dependencies:1.5.8.RELEASE"))

        // define dependencies without versions
        implementation("com.google.code.gson:gson")
        implementation("dom4j:dom4j")

        // this version will be overridden by the one found in the BOM
        implementation("org.codehaus.groovy:groovy:1.8.6")
    }

| Using `enforcedPlatform` needs to be considered with care if your software component can be consumed by others. This declaration is effectively transitive and so will apply to the dependency graph of your consumers. Unfortunately they will have to use `exclude` if they happen to disagree with one of the forced versions. Instead, if your reusable software component has a strong opinion on some third party dependency versions, consider using a rich version declaration with a `strictly`.

## Should I use a platform or a catalog?

Because platforms and catalogs both talk about dependency versions and can both be used to share dependency versions in a project, there might be a confusion regarding what to use and if one is preferable to the other.

In short, you should:

- use catalogs to only define dependencies and their versions for projects and to generate type-safe accessors

- use platform to apply versions to dependency graph and to affect dependency resolution

A catalog helps with centralizing the dependency versions and is only, as it name implies, a catalog of dependencies you can pick from. We recommend using it to declare the coordinates of your dependencies, in all cases. It will be used by Gradle to generate type-safe accessors, present short-hand notations for external dependencies and it allows sharing those coordinates between different projects easily. Using a catalog will not have any kind of consequence on downstream consumers: it’s transparent to them.

A platform is a more heavyweight construct: it’s a component of a dependency graph, like any other library. If you depend on a platform, that platform is itself a component in the graph. It means, in particular, that:

- Constraints defined in a platform can influence transitive dependencies, not only the direct dependencies of your project.

- A platform is versioned, and a transitive dependency in the graph can depend on a different version of the platform, causing various dependency upgrades.

- A platform can tie components together, and in particular can be used as a construct for aligning versions.

- A dependency on a platform is "inherited" by the consumers of your dependency: it means that a dependency on a platform can influence what versions of libraries would be used by your consumers even if you don’t directly, or transitively, depend on components the platform references.

In summary, using a catalog is always a good engineering practice as it centralizes common definitions, allows sharing of dependency versions or plugin versions, but it is an "implementation detail" of the build: it will not be visible to consumers and unused elements of a catalog are just ignored.

A platform is meant to influence the dependency resolution graph, for example by adding constraints on transitive dependencies: it’s a solution for structuring a dependency graph and influencing the resolution result.

In practice, your project can both use a catalog and declare a platform which itself uses the catalog:

Example 22. Using a catalog within a platform definition
build.gradle

    plugins {
        id 'java-platform'
    }

    dependencies {
        constraints {
            api(libs.mylib)
        }
    }

build.gradle.kts

    plugins {
        `java-platform`
    }

    dependencies {
        constraints {
            api(libs.mylib)
        }
    }