Best Practice Using Gradle Platforms and hiding version

In a typical Spring Boot project, we typically find the following plugin is installed.

plugins {
    id 'org.springframework.boot' version '3.4.2'
}

This allows us to use ./gradlew bootRun on the command line, which is excellent. However, if we’ve created a platform that wraps all the spring boot starters to ensure version numbers are consistent and supported internally across all teams using the platform, then this becomes a problem because we end up with this (simplified)

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.4.2'
}

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation platform('com.example:platform-boot-starter:0.0.1-SNAPSHOT') 
}

The com.example platform project has all our spring versions and dependencies in it, but if I remove the version from the plugin, I can no longer ru.n

./gradlew build bootRun

I was able to remove the version if I created a buildSrc/build.gradle file and add the following

plugins {
    id 'groovy-gradle-plugin'
}

repositories {
    mavenLocal()
    gradlePluginPortal()
}

dependencies {
    implementation(platform("com.example:platform-parent:0.0.1-SNAPSHOT"))
}

I’d like to have no Spring versions required anywhere within any consumers of the platform because it’s meant to be entirely controlled by the platform, which resides in an independent repo and is managed by a different team. I’ve tried various things, like using version catalogs, and have had no luck with anything except the above method, which seems quite ugly. I’m new to Gradle, so I suspect I have a lot to learn and am likely doing it completely wrong.

Is there a way to achieve the same result without requiring a “buildSrc” directory?

To use only the build script and use the platform, you have to switch to the legacy way of adding plugins to the classpath and applying them like this:

buildscript {
    dependencies {
        classpath(platform('com.example:platform-parent:0.0.1-SNAPSHOT'))
        classpath('org.springframework.boot:org.springframework.boot.gradle.plugin')
    }
}
apply(plugin: 'org.springframework.boot')

While discouraged it would at least work acceptably in a Groovy DSL build script that you are using.
In a Kotlin DSL build script (highly recommended by me) it would not be so nice, as you then do not get accessors to configure the plugin and need to do it by type which is uglier and less idiomatic.

Indeed a version catalog would be the better way to do this.

Platforms are mainly suited for two use-cases.

  1. to control the versions of transitive dependencies
  2. to make sure multiple projects of one build are always used with the same version at runtime (version alignment) at least when consumed by Gradle

Version catalogs are suited for “here you have a catalog of coordinates with versions from which you can pick what you need”.
As these can also be published, your consumers can then “import” this published version catalog and can then apply the Spring Boot plugin version that is declared in the version catalog like

plugins {
    alias(externallyControlled.plugins.spring.boot)
}

or similar.

Are you aware of any open-source projects that use the version catalog like this? I’d rather not do anything legacy because I don’t want the headache of having to move it all later when it gets deprecated.

I tried to have a version catalog in a separate repo along with the platform, but I couldn’t get any of the alias stuff to work in the projects that had it as a dependency.

Platform and version catalog are two independent things. Just because you depend on the platform does not mean that any version catalog is there or the other way around.

I don’t have an example at hand, but just have a look at the version catalog documentation. It shows how you create and publish the version catalog and how you consume a published version catalog.

Ah, wait, I’m stupid, just take one of my projects as example. :smiley:

Perfect. I managed to get it all working. I created a version catalog and then, under that, made the spring starters as platforms to hide the Spring versions from the platform consumers.

Thanks for your help.

1 Like