Table of Contents
Managing Dependency Versions in Gradle
When developing software, you rarely write all the code from scratch. Usually, libraries or frameworks are leveraged, allowing developers to use numerous features without building everything from the ground up. These codes can face various issues, such as deprecated features, regressions, or version conflicts between dependencies. Hence, managing stable versions is crucial.
The most fundamental method of version management in gradle is explicitly specifying the version:
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-html:0.9.1")
}
Problem 1: Managing Dependency Versions in Multi-Module Projects
In each dependency, the version is specified explicitly. This approach is straightforward for single-module projects. However, in multi-module projects, where multiple build.gradle
files are created, this necessitates repeating the version specification.
// moduleA/build.gradle.kts
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-html:0.9.1")
}
// moduleB/build.gradle.kts
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-html:0.9.1")
}
If you wish to upgrade the version, both files need modification. To streamline this, versions can be managed in a separate file as follows:
// gradle.properties
kotlinXVersion=0.9.1
// moduleA/build.gradle.kts
val kotlinXVersion: String by project
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-html:${kotlinXVersion}")
}
// moduleB/build.gradle.kts
val kotlinXVersion: String by project
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-html:${kotlinXVersion}")
}
With this setup, updating the gradle.properties
file changes the version across all modules. However, there is still some boilerplate code, such as declaring the kotlinXVersion
variable.
Problem 2: Managing Plugin Versions
Plugin version management differs slightly. In single-module projects, plugins are typically used as follows:
// build.gradle.kts
plugins {
kotlin("jvm") version 1.8.22
}
To manage plugin versions in gradle.properties
, along with other dependencies, a different approach is required. Unfortunately, explicit version writing is the only option in the plugins
block. Therefore, separate management in settings.gradle
is needed, which can be inconvenient.
// gradle.properties
kotlinVersion=1.8.22
// settings.gradle.kts
pluginManagement {
val kotlinVersion: String by settings
plugins {
kotlin("jvm") version kotlinVersion
}
}
// build.gradle.kts
plugins {
kotlin("jvm")
}
Additionally, in traditional Gradle, dependency versions could be managed through ext
, buildSrc
, composite build
, but these methods also involve navigating multiple files.
Gradle Version Catalog
This is where version catalog comes into play. Gradle introduced the Version Catalog feature in Gradle 7.4, enabling the management of multiple dependencies in one place. It also generates type-safe accessors, eliminating the need for repeated string writing.
Creating a Version Catalog
// gradle.properties
kotlinVersion=1.8.22
springBootVersion=3.2.0
// settings.gradle.kts
dependencyResolutionManagement {
val kotlinVersion: String by settings
val springBootVersion: String by settings
versionCatalogs {
create("kt") {
plugin("jvm", "org.jetbrains.kotlin.jvm").version(kotlinVersion)
}
create("spring") {
plugin("kotlin", "org.jetbrains.kotlin.plugin.spring").version(kotlinVersion)
plugin("boot", "org.springframework.boot").version(springBootVersion)
plugin("dependencyManagement", "io.spring.dependency-management").version(springDependencyManagementVersion)
library("boot-starter-jpa", "org.springframework.boot", "spring-boot-starter-data-jpa").version(springBootVersion)
library("boot-starter-thymeleaf", "org.springframework.boot", "spring-boot-starter-thymeleaf").version(springBootVersion)
library("boot-starter-web", "org.springframework.boot", "spring-boot-starter-web").version(springBootVersion)
}
}
create({catalogName})
: creates catalog with specified nameplugin({alias}, {id}).version({version})
: register plugin with alias and versionlibrary({alias}, {group}, {artifact}).version({version})
: set alias and version of dependency. If explicit version is not required (due to dependency management in place etc.), use.withoutVersion()
Using the Version Catalog
Once the version catalog is set up, it’s time to use it. Plugins can be utilized with automatically generated type-safe accessors through alias()
. In the version catalog, aliases with hyphens (-
) are transformed into dots (.
)
plugins {
alias(kt.plugins.jvm)
alias(spring.plugins.kotlin)
alias(spring.plugins.boot)
alias(spring.plugins.dependencyManagement)
}
Note: If the project builds fine, but IDE complains with
https://github.com/gradle/gradle/issues/22797can't be called by implicit receiver
, it might be gradle regression issue. Updating your gradle version might fix it.
For dependencies, simply use the type-safe accessors directly.
dependencies {
implementation(spring.boot.starter.jpa)
implementation(spring.boot.starter.thymeleaf)
implementation(spring.boot.starter.web)
}
Note: In Kotlin DSL, blocks like
allprojects
orsubprojects
cannot directly use accessors; they must be called throughrootProject
.https://github.com/gradle/gradle/issues/16634allprojects { dependencies { implementation(rootProject.spring.boot.starter.jpa) implementation(rootProject.spring.boot.starter.thymeleaf) implementation(rootProject.spring.boot.starter.web) } }
Conclusion
Version catalog enables easier global dependency management compared to the traditional methods. I’ve covered the very basic features of version catalog here in this article. Though not covered in this article, the bundle feature is also available, which allows grouping several dependencies for unified use. For more details on bundle and other features, refer to Gradle’s official documentation: Gradle Platforms.
Good!