Managing Dependency Versions in Gradle
When developing a software, you rarely write all the code from scratch. Usually, libraries or frameworks are used, allowing developers to use numerous features without building everything from the ground up. Mismanagement these dependencies can lead to broken build or application, so it is important to manage them properly.
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 of build.gradle
script. Instead, 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")
}
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 withcan't be called by implicit receiver
, it might be gradle regression issue. Updating your gradle version might fix it.
https://github.com/gradle/gradle/issues/22797
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 likeallprojects
orsubprojects
cannot directly use accessors; they must be called throughrootProject
.
https://github.com/gradle/gradle/issues/16634
allprojects {
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!