Publish your Gradle Version Catalog

Benjamin Hoogterp
4 min readFeb 13, 2023

--

Since version 7.4.2, Gradle has allowed in its standard features the inclusion of a libs.versions.toml file (TOML stands for Tom’s Obvious Markup Language, a simple and “obvious” markup format to keep things, well, simple). More direct information can be found here…

This version catalog can contain a list of dependency versions, including plugins, which can greatly simplify your dependency management. When placed in the file ./gradle/libs.versions.toml, Gradle will automatically detect it and allow you to use alias from that file to fill control your dependencies. For example, given the following file:

[versions]
checkstyle = "10.6.0"
jacoco = "0.8.8"

jacksonVersion = "2.14.1"
jacksonDatabindNullableVersion = "0.2.4"

[libraries]
jackson-core = { module = "com.fasterxml.jackson.core:jackson-core", version.ref = "jacksonVersion" }
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jacksonVersion" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jacksonVersion" }
jackson-datatype-joda = { module = "com.fasterxml.jackson.datatype:jackson-datatype-joda", version.ref = "jacksonVersion" }
jackson-datatype-jsr310 = { module = "com.fasterxml.jackson.datatype:jackson-datatype-jsr310", version.ref = "jacksonVersion" }
jackson-dataformat-xml = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-xml", version.ref = "jacksonVersion" }
jackson-jaxrs = { module = "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider", version.ref = "jacksonVersion" }
jackson-databind-nullable = { module = "org.openapitools:jackson-databind-nullable", version.ref = "jacksonDatabindNullableVersion" }

log4j-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4jVersion" }
log4j-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4jVersion" }

commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.12.0" }

[bundles]
jackson = ["jackson-core", "jackson-annotations", "jackson-databind", "jackson-datatype-joda", "jackson-datatype-jsr310", "jackson-jaxrs", "jackson-databind-nullable"]
log4j = ["log4j-core", "log4j-api"]

[plugins]
sonarqube = { id = "org.sonarqube", version = "3.5.0.2730" }

You can then reference these dependencies and versions using the libs object that Gradle will create for you. Instead of ‘implementation “<group>:<artifact>:<version>”’, you write ‘implementation libs.artifact.id’

See the example below:

plugins {
id "java-library"
id "jacoco"
id "checkstyle"
alias libs.plugins.sonarqube
id "maven-publish"
}

checkstyle.toolVersion libs.versions.checkstyle.get()
jacoco.toolVersion = libs.versions.jacoco.get()

repositories {
google(); mavenCenral()
}

dependencies {
// ...
implementation libs.bundles.log4j
implementation libs.bundles.jackson

implementation libs.commons.lang3
// ...
}

This approach reduces all of your dependency management to a single location (although not a “Single Source of Truth”, as Gradle may still decide to use a different version based on its resolution strategy), even across multi-module Gradle builds, the toml file is only required at the root level.

Distribution

But, what about sharing this information for other projects for distributed version management? One approach would be to build a BOM package, and distribute it that way. But, another option would be to simply publish your .toml file directly, and consume it in another gradle project through a shared repository.

You need to use two plugins:

  • maven-publish — to actually publish the .toml file.
  • version-catalog — so that the publishing knows its a toml publication.

In addition, in its current iteraion (Gradle 8.0), you also need to first load the catalog, otherwise your publication will be empty.

plugins {
id "maven-publish"
id "version-catalog"
}

// ...


// This is required, otherwise your published .toml file will be empty
catalog {
versionCatalog {
from(files("$rootDir/gradle/libs.versions.toml"))
}
}

// This configures the maven-publish plugin
publishing {
publications {
maven(MavenPublication) {
group = "com.example.toml"
version = "1.2.3.4.5" // whatever version you want
artifactId = 'my-version-catalog'
// This is important so the published artifact registers as a .toml file, rather than a library
from components.versionCatalog
}
}
repositories {
maven {
name = "myRepositoryServer"
url myRepoUrl
credentials {
// fill in as required...
}
}
}
}

Running the ./gradlew clean publish command will publish this artifact, the libs.versions.toml file, to your repository.

Consuming a Distributed TOML

To consume a TOML file you have previously published, you will place the import into your settings.gradle file:

// settings.gradle

// You need this plugin to bring in the catalog
pluginManagement {
plugins {
id "version-catalog"
}
}

// Include normal project setup
rootProject.name = "my-project"

// Any multi-module build modules here
include 'application', 'server', 'client'

// Finally, import your catalog in this block
dependencyResolutionManagement {
repositories {
google(); mavenCentral()
// Any private repositories here
}

versionCatalogs {
libs {
from "com.example.toml:my-version-catalog:<version>"
}
}
}

Once imported into the consumer project, you can then reference the dependencies described in the TOML with the same notation:

dependencies {
// ...
implementation libs.bundles.log4j
implementation libs.bundles.jackson
// ...
}

In this way, you can centrally locate and distribute your version catalogs to help manage versions across a multi-module as well as a multi-project environment.

--

--