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:

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.