# Platforms

version 9.8.0-milestone-2

Platforms ensure that all dependencies in a project use a coordinated set of versions.

Platforms help you manage and enforce version consistency across modules or libraries, especially when working with a set of related dependencies that need to stay in sync.

| Platforms influence dependency resolution by applying or enforcing versions across the dependency graph. Version catalogs also centralize dependency versions but do not affect resolution — they provide type-safe accessors and consistent coordinates. For guidance on when to use each approach, see Using Catalogs with Platforms.

## Using a Platform

A platform is a specialized software component that controls transitive dependency versions. Typically, it consists of dependency constraints that either recommend or enforce specific versions. Platforms are particularly useful when you need to share consistent dependency versions across multiple projects.

For example, a local `platform` project looks as follows:

build.gradle.kts

    dependencies {
        constraints {
            // Platform declares some versions of libraries
            api("commons-httpclient:commons-httpclient:3.1")
            api("org.apache.commons:commons-lang3:3.8.1")
        }
    }

build.gradle

    dependencies {
        constraints {
            // Platform declares some versions of libraries
            api 'commons-httpclient:commons-httpclient:3.1'
            api 'org.apache.commons:commons-lang3:3.8.1'
        }
    }

To use a platform, declare a dependency with the `platform` keyword in your `dependencies {}` block:

build.gradle.kts

    dependencies {
        // A local platform
        api(platform(project(":platform"))) // get recommended versions from the platform project
        api("commons-httpclient:commons-httpclient") // no version required in declaration (comes from bom)

        // A published platform
        implementation(platform("org.junit:junit-bom:5.10.0")) // get recommended versions from the platform project
        testImplementation("org.junit.jupiter:junit-jupiter") // no version required in declaration (comes from bom)
    }

build.gradle

    dependencies {
        // A local platform
        api platform(project(':platform')) // get recommended versions from the platform project
        api 'commons-httpclient:commons-httpclient' // no version required in declaration (comes from bom)

        // A published platform
        implementation platform('org.junit:junit-bom:5.10.0') // get recommended versions from the platform project
        testImplementation 'org.junit.jupiter:junit-jupiter' // no version required in declaration (comes from bom)
    }

For details of the published `junit` platform, see its Maven repository page.

The `platform` notation automatically performs several actions:

- Sets the `org.gradle.category` attribute to platform, ensuring Gradle selects the platform component. See the Attributes documentation to learn more.

- Enables the `endorseStrictVersions` behavior by default, enforcing strict versions defined in the platform. Check out the Strict Version Enforcement section below to learn more.

Custom platforms like the `platform` project above are created with the `java-platform plugin` and can be published as Maven BOMs (Bill of Materials) for consumption by other projects (such as the `junit-bom`).

## Creating a Platform

In Java projects, the `java-platform` plugin combined with dependency constraints can be used to create a platform:

build.gradle.kts

    plugins {
        `java-platform`
    }

    dependencies {
        constraints {
            api("com.google.code.gson:gson:2.10.1")
            api("org.apache.commons:commons-lang3:3.14.0")
            api("org.slf4j:slf4j-api:2.0.9")
        }
    }

build.gradle

    plugins {
        id 'java-platform'
    }

    dependencies {
        constraints {
            api 'com.google.code.gson:gson:2.10.1'
            api 'org.apache.commons:commons-lang3:3.14.0'
            api 'org.slf4j:slf4j-api:2.0.9'
        }
    }

Subprojects that depend on this platform inherit the declared versions without specifying them directly.

If used as a regular platform, this platform would set the following constraints:

    > Task :showConstraints

    Constraints published by this platform:
      com.google.code.gson:gson:2.10.1
      org.apache.commons:commons-lang3:3.14.0
      org.slf4j:slf4j-api:2.0.9

If used as an enforced platform, this platform would set the same constraints with a `strictly` on the version.

## Importing a Platform (BOM)

Gradle supports importing BOMs, which are POM files containing `<dependencyManagement>` sections that manage dependency versions.

Traditionally, a BOM is a POM file with `<packaging>pom</packaging>` and a `<dependencyManagement>` block. Gradle, however, derives a platform variant from every Maven POM it consumes, so any POM with a `<dependencyManagement>` block can be imported as a platform, not just a traditional BOM.

Gradle treats all `<dependencyManagement>` entries in a POM consumed as a platform as constraints, like Adding Constraints On Dependencies.

### Regular Platform

To import a BOM, declare a dependency on it using the `platform` dependency modifier method:

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

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'
    }

In this example, the Spring Boot BOM provides the versions for `gson` and `dom4j`, so no explicit versions are needed.

### Enforced Platform

When you import a BOM using `platform`, the versions it defines act as recommendations; they can be upgraded by other dependencies in the graph. To prevent upgrades and lock all versions from a BOM, use `enforcedPlatform`:

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

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'
    }

Under the hood, `enforcedPlatform` converts every version constraint from the BOM into a `strictly` declaration. This means that no dependency in the graph can be upgraded past the versions defined in the BOM.

| You can verify this on your own build: `./gradlew dependencyInsight --dependency <module>` shows the enforced version with a `{strictly <version>}` annotation, and `./gradlew dependencies` shows the platform itself pinned with `{strictly <version>}`.

## `enforcedPlatform` vs. `strictly`

A good rule to follow:

- Use `enforcedPlatform` to pin all versions from a BOM as a group.

- Use `strictly` on individual dependencies when you only need to control one or two specific modules.

Using `platform` together with a `strictly` constraint on individual modules can bypass the BOM’s coordinated version management. The example below imports a BOM with `platform` and applies `strictly` to only one module from the group. The resolved versions are then driven by the `strictly` constraint and its transitive dependencies rather than the BOM:

build.gradle.kts

    dependencies {
        // BOM recommends log4j-api:2.16.0 and log4j-core:2.16.0
        implementation(platform("org.apache.logging.log4j:log4j-bom:2.16.0"))

        // The strictly constraint overrides the BOM for log4j-core
        implementation("org.apache.logging.log4j:log4j-core") {
            version {
                strictly("[2.17, 3[")
                prefer("2.17.0")
            }
        }
    }

build.gradle

    dependencies {
        // BOM recommends log4j-api:2.16.0 and log4j-core:2.16.0
        implementation platform('org.apache.logging.log4j:log4j-bom:2.16.0')

        // The strictly constraint overrides the BOM for log4j-core
        implementation('org.apache.logging.log4j:log4j-core') {
            version {
                strictly '[2.17, 3['
                prefer '2.17.0'
            }
        }
    }

Here, `log4j-core` resolves to `2.17.0` because of the `strictly` constraint, and `log4j-api` is also pulled to `2.17.0` as a transitive dependency of `log4j-core`, not because the BOM recommended it:

    runtimeClasspath - Runtime classpath of source set 'main'.
    +--- org.apache.logging.log4j:log4j-bom:2.16.0
    |    +--- org.apache.logging.log4j:log4j-core:2.16.0 -> 2.17.0 (c)
    |    \--- org.apache.logging.log4j:log4j-api:2.16.0 -> 2.17.0 (c)
    \--- org.apache.logging.log4j:log4j-core:{strictly [2.17, 3[; prefer 2.17.0} -> 2.17.0
         \--- org.apache.logging.log4j:log4j-api:2.17.0

With modules that do not share a transitive relationship, siblings could remain at the older BOM version, leading to mismatched versions within the same group.

To keep the group’s versions coordinated, either upgrade the BOM version itself, or use `enforcedPlatform` with the newer BOM:

build.gradle.kts

    dependencies {
        // Pins all Log4j modules to 2.17.0
        implementation(enforcedPlatform("org.apache.logging.log4j:log4j-bom:2.17.0"))
        //implementation(enforcedPlatform("org.apache.logging.log4j:log4j-bom:2.16.0")) <-- This would fail resolution

        // The strictly constraint overrides the BOM for log4j-core
        implementation("org.apache.logging.log4j:log4j-core") {
            version {
                strictly("[2.17, 3[")
                prefer("2.17.0")
            }
        }
    }

build.gradle

    dependencies {
        // Pins all Log4j modules to 2.17.0
        implementation enforcedPlatform('org.apache.logging.log4j:log4j-bom:2.17.0')
        // implementation enforcedPlatform('org.apache.logging.log4j:log4j-bom:2.16.0') <-- This would fail resolution

        implementation('org.apache.logging.log4j:log4j-core') {
            version {
                strictly '[2.17, 3['
                prefer '2.17.0'
            }
        }
    }

| `enforcedPlatform` should generally only be used in applications, not in libraries or other components consumed by others.

Because `enforcedPlatform` converts all BOM constraints to `strictly`, these strict versions propagate transitively to every consumer of your project. Consumers who need a different version cannot simply declare it. They must either override with their own `strictly` higher in the dependency graph, or apply a component metadata rule that calls `doNotEndorseStrictVersions()` on the transitive platform dependency.

If you are publishing a library that depends on specific third-party versions, prefer using rich version declarations with `strictly` and a range on only the dependencies that require it:

build.gradle.kts

    dependencies {
        // Library-friendly: constrains only the module you need, with a flexible range
        implementation("org.apache.logging.log4j:log4j-core") {
            version {
                strictly("[2.17, 3[")
                prefer("2.17.1")
            }
        }
    }

build.gradle

    dependencies {
        // Library-friendly: constrains only the module you need, with a flexible range
        implementation('org.apache.logging.log4j:log4j-core') {
            version {
                strictly '[2.17, 3['
                prefer '2.17.1'
            }
        }
    }

This gives consumers the flexibility to choose any compatible version within the range.

## Strict Version Endorsement

With platforms, by default, `strictly` versions declared in the platform are handled as if the consuming project declared them directly. This matters because `strictly` has significant consequences for the resolved graph. This behavior is controlled by `endorseStrictVersions`, which `platform()` enables by default.

Without endorsement, a platform’s `strictly` constraints are treated as regular version recommendations in the consumer’s dependency graph; they will not prevent transitive upgrades outside the subgraph the platform controls.

If you do not need the platform’s strict constraints to propagate, you can disable endorsement:

    dependencies {
        // The platform's strictly declarations will NOT be enforced in this project.
        // Transitive dependencies may upgrade past the versions the platform intended to pin.
        implementation(platform("com.example:my-platform:1.0")) {
            doNotEndorseStrictVersions()
        }
    }

    dependencies {
        // The platform's strictly declarations will NOT be enforced in this project.
        // Transitive dependencies may upgrade past the versions the platform intended to pin.
        implementation(platform('com.example:my-platform:1.0')) {
            doNotEndorseStrictVersions()
        }
    }

| If you create a platform with `strictly` constraints and your consuming project does not seem to enforce them, verify that `endorseStrictVersions` has not been disabled. The `platform()` keyword enables it by default, but importing a platform without this keyword (e.g., as a regular dependency) will not endorse its strict versions.