The Java Platform plugin brings the ability to declare platforms for the Java ecosystem. A platform can be used for different purposes:

  • a description of modules which are published together (and for example, share the same version)

  • a set of recommended versions for heterogeneous libraries. A typical example includes the Spring Boot BOM

  • sharing a set of dependency versions between subprojects

A platform is a special kind of software component which doesn’t contain any sources: it is only used to reference other libraries, so that they play well together during dependency resolution.

Platforms can be published as Maven BOMs or with the experimental Gradle metadata file format.

The java-platform plugin cannot be used in combination with the java or java-library plugins in a given project. Conceptually a project is either a platform, with no binaries, or produces binaries.

Usage

To use the Java Platform plugin, include the following in your build script:

Example 1. Using the Java Platform plugin
build.gradle
plugins {
    id 'java-platform'
}
build.gradle.kts
plugins {
    `java-platform`
}

API and runtime separation

A major difference between a Maven BOM and a Java platform is that in Gradle dependencies and constraints are declared and scoped to a configuration and the ones extending it. While many users will only care about declaring constraints for compile time dependencies, thus inherited by runtime and tests ones, it allows declaring dependencies or constraints that only apply to runtime or test.

For this purpose, the plugin exposes two configurations that can be used to declare dependencies: api and runtime. The api configuration should be used to declare constraints and dependencies which should be used when compiling against the platform, whereas the runtime configuration should be used to declare constraints or dependencies which are visible at runtime.

Example 2. Declaring API and runtime constraints
build.gradle
dependencies {
    constraints {
        api 'commons-httpclient:commons-httpclient:3.1'
        runtime 'org.postgresql:postgresql:42.2.5'
    }
}
build.gradle.kts
dependencies {
    constraints {
        api("commons-httpclient:commons-httpclient:3.1")
        runtime("org.postgresql:postgresql:42.2.5")
    }
}

Note that this example makes use of constraints and not dependencies. In general, this is what you would like to do: constraints will only apply if such a component is added to the dependency graph, either directly or transitively. This means that all constraints listed in a platform would not add a dependency unless another component brings it in: they can be seen as recommendations.

For example, if a platform declares a constraint on org:foo:1.1, and that nothing else brings in a dependency on foo, foo will not appear in the graph. However, if foo appears, then usual conflict resolution would kick in. If a dependency brings in org:foo:1.0, then we would select org:foo:1.1 to satisfy the platform constraint.

By default, in order to avoid the common mistake of adding a dependency in a platform instead of a constraint, Gradle will fail if you try to do so. If, for some reason, you also want to add dependencies in addition to constraints, you need to enable it explicitly:

Example 3. Allowing declaration of dependencies
build.gradle
javaPlatform {
    allowDependencies()
}
build.gradle.kts
javaPlatform {
    allowDependencies()
}

Local project constraints

If you have a multi-project build and want to publish a platform that links to subprojects, you can do it by declaring constraints on the subprojects which belong to the platform, as in the example below:

Example 4. Declaring constraints on subprojects
build.gradle
dependencies {
    constraints {
        api project(":core")
        api project(":lib")
    }
}
build.gradle.kts
dependencies {
    constraints {
        api(project(":core"))
        api(project(":lib"))
    }
}

The project notation will become a classical group:name:version notation in the published metadata.

Sourcing constraints from another platform

Sometimes the platform you define is an extension of another existing platform.

In order to have your platform include the constraints from that third party platform, it needs to be imported as a platform dependency:

Example 5. Importing a platform
build.gradle
javaPlatform {
    allowDependencies()
}

dependencies {
    api platform('com.fasterxml.jackson:jackson-bom:2.9.8')
}
build.gradle.kts
javaPlatform {
    allowDependencies()
}

dependencies {
    api(platform("com.fasterxml.jackson:jackson-bom:2.9.8"))
}

Publishing platforms

Publishing Java platforms is done by applying the maven-publish plugin and configuring a Maven publication that uses the javaPlatform component:

Example 6. Publishing as a BOM
build.gradle
publishing {
    publications {
        myPlatform(MavenPublication) {
            from components.javaPlatform
        }
    }
}
build.gradle.kts
publishing {
    publications {
        create<MavenPublication>("myPlatform") {
            from(components["javaPlatform"])
        }
    }
}

This will generate a BOM file for the platform, with a <dependencyManagement> block where its <dependencies> correspond to the constraints defined in the platform module.

Consuming platforms

Because a Java Platform is a special kind of component, a dependency on a Java platform has to be declared using the platform or enforcedPlatform keyword, as explained in the managing transitive dependencies section. For example, if you want to share dependency versions between subprojects, you can define a platform module which would declare all versions:

Example 7. Recommend versions in a platform module
build.gradle
dependencies {
    constraints {
        // Platform declares some versions of libraries used in subprojects
        api 'commons-httpclient:commons-httpclient:3.1'
        api 'org.apache.commons:commons-lang3:3.8.1'
    }
}
build.gradle.kts
dependencies {
    constraints {
        // Platform declares some versions of libraries used in subprojects
        api("commons-httpclient:commons-httpclient:3.1")
        api("org.apache.commons:commons-lang3:3.8.1")
    }
}

And then have subprojects depend on the platform to get recommendations:

Example 8. Get recommendations from 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")
}