// Copyright (C) 2024 Gradle, Inc.
//
// Licensed under the Creative Commons Attribution-Noncommercial-ShareAlike 4.0 International License.;
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://creativecommons.org/licenses/by-nc-sa/4.0/
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

[[platforms]]
= Platforms

++++
<script>
const currentAnchor = window.location.hash.substring(1); // Remove the '#' symbol

const redirectionRules = {
"sub::toml-dependencies-format": "version_catalogs.html#sec::toml-dependencies-format",
};

if (currentAnchor && redirectionRules[currentAnchor]) {
window.location.href = redirectionRules[currentAnchor];
}
</script>
++++

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

[[sec:sharing-dep-versions-between-projects]]
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.

NOTE: Platforms influence dependency resolution by applying or enforcing versions across the dependency graph. <<version_catalogs.adoc#version-catalog,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 <<centralizing_catalog_platform.adoc#platforms-vs-catalog,Using Catalogs with Platforms>>.

[[sec:using-platform-to-control-transitive-deps]]
== Using a Platform

A platform is a specialized software component that controls transitive dependency versions.
Typically, it consists of <<dependency_constraints.adoc#sec:adding-constraints-transitive-deps,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:

====
include::sample[dir="snippets/reference/dependency-management/centralizing-dependencies/recommender/kotlin/platform",files="build.gradle.kts[tags=define-platform]"]
include::sample[dir="snippets/reference/dependency-management/centralizing-dependencies/recommender/groovy/platform",files="build.gradle[tags=define-platform]"]
====

To use a platform, declare a dependency with the link:{javadocPath}/org/gradle/api/artifacts/dsl/DependencyHandler.html#platform(java.lang.Object)[`platform`] keyword in your `dependencies {}` block:

====
include::sample[dir="snippets/reference/dependency-management/centralizing-dependencies/recommender/kotlin/consumer",files="build.gradle.kts[tags=get-recommendations]"]
include::sample[dir="snippets/reference/dependency-management/centralizing-dependencies/recommender/groovy/consumer",files="build.gradle[tags=get-recommendations]"]
====

For details of the published `junit` platform, see its link:https://mvnrepository.com/artifact/org.junit/junit-bom/5.10.0[Maven repository page].

The `platform` notation automatically performs several actions:

- Sets the link:{javadocPath}/org/gradle/api/attributes/Category.html[`org.gradle.category` attribute] to platform, ensuring Gradle selects the platform component. See the <<variant_attributes.adoc#variant-attributes,Attributes>> documentation to learn more.
- Enables the link:{javadocPath}/org/gradle/api/artifacts/ModuleDependency.html#endorseStrictVersions--[`endorseStrictVersions`] behavior by default, enforcing strict versions defined in the platform. Check out the <<#sec:endorsing-platform-strict-versions,Strict Version Enforcement section below>> to learn more.

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

[[sec:creating-platform]]
== Creating a Platform

In Java projects, the `java-platform` plugin combined with <<dependency_constraints.adoc#dependency-constraints,dependency constraints>> can be used to create a platform:

====
include::sample[dir="snippets/java-platform/createPlatform/kotlin",files="build.gradle.kts[tags=create-platform]"]
include::sample[dir="snippets/java-platform/createPlatform/groovy",files="build.gradle[tags=create-platform]"]
====

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

If used as a <<#sec:regular-platform,regular platform>>, this platform would set the following constraints:

[source,text]
----
include::{snippetsPath}/java-platform/createPlatform/tests/create-platform.out[]
----

If used as an <<#sec:enforced-platform,enforced platform>>, this platform would set the same constraints with a `strictly` on the version.

[[sec:bom-import]]
== Importing a Platform (BOM)

Gradle supports importing https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Bill_of_Materials_.28BOM.29_POMs[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 <<dependency_constraints.adoc#sec:adding-constraints-transitive-deps,Adding Constraints On Dependencies>>.

[[sec:regular-platform]]
=== Regular Platform

To import a BOM, declare a dependency on it using the link:{javadocPath}/org/gradle/api/artifacts/dsl/DependencyHandler.html#platform(java.lang.Object)[`platform`] dependency modifier method:

====
include::sample[dir="snippets/reference/dependency-management/centralizing-dependencies/managingTransitiveDependencies-constraintsFromBOM/kotlin",files="build.gradle.kts[tags=dependency-on-bom]"]
include::sample[dir="snippets/reference/dependency-management/centralizing-dependencies/managingTransitiveDependencies-constraintsFromBOM/groovy",files="build.gradle[tags=dependency-on-bom]"]
====

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

[[sec:enforced-platform]]
=== Enforced Platform

When you import a BOM using link:{javadocPath}/org/gradle/api/artifacts/dsl/DependencyHandler.html#platform(java.lang.Object)[`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 link:{javadocPath}/org/gradle/api/artifacts/dsl/DependencyHandler.html#enforcedPlatform(java.lang.Object)[`enforcedPlatform`]:

====
include::sample[dir="snippets/reference/dependency-management/centralizing-dependencies/managingTransitiveDependencies-enforcedConstraintsFromBOM/kotlin",files="build.gradle.kts[tags=dependency-on-bom]"]
include::sample[dir="snippets/reference/dependency-management/centralizing-dependencies/managingTransitiveDependencies-enforcedConstraintsFromBOM/groovy",files="build.gradle[tags=dependency-on-bom]"]
====

Under the hood, `enforcedPlatform` converts every version constraint from the BOM into a <<dependency_versions.adoc#sec:strict-version,`strictly`>> declaration.
This means that no dependency in the graph can be upgraded past the versions defined in the BOM.

TIP: 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>}`.

[[sec:enforced-platform-vs-strictly]]
== `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:

====
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-platformWithStrictlyMisalignment/kotlin",files="build.gradle.kts[tags=platform-strictly-misalignment]"]
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-platformWithStrictlyMisalignment/groovy",files="build.gradle[tags=platform-strictly-misalignment]"]
====

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:

[source,text]
----
include::{snippetsPath}/dependencyManagement/managingTransitiveDependencies-platformWithStrictlyMisalignment/tests/platform-strictly-misalignment.out[]
----

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:

====
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-enforcedPlatformAlignment/kotlin",files="build.gradle.kts[tags=enforced-platform-alignment]"]
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-enforcedPlatformAlignment/groovy",files="build.gradle[tags=enforced-platform-alignment]"]
====

[[sec:enforced-platform-caution]]
WARNING: `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_rules.adoc#component-metadata-rules,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 <<dependency_versions.adoc#sec:rich-version-constraints,rich version declarations>> with `strictly` and a range on only the dependencies that require it:

====
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-libraryFriendlyStrictly/kotlin",files="build.gradle.kts[tags=library-friendly-strictly]"]
include::sample[dir="snippets/dependencyManagement/managingTransitiveDependencies-libraryFriendlyStrictly/groovy",files="build.gradle[tags=library-friendly-strictly]"]
====

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

[[sec:endorsing-platform-strict-versions]]
== 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 <<dependency_versions.adoc#sec:strict-version-consequences,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:

====
[.multi-language-sample]
=====
[source,kotlin]
----
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()
    }
}
----
=====
[.multi-language-sample]
=====
[source,groovy]
----
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()
    }
}
----
=====
====

TIP: 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.
