Best Practices for Dependencies
Use Version Catalogs to Centralize Dependency Versions
Use Version Catalogs provide a centralized, declarative way to manage dependency versions throughout a build.
Explanation
When you define your dependency versions in a single, shared version catalog, you reduce duplication and make upgrades easier.
Instead of changing dozens of build.gradle(.kts)
files, you update the version in one place.
This simplifies maintenance, improves consistency, and reduces the risk of accidental version drift between modules.
Consistent version declarations across projects also make it easier to reason about behavior during testing—especially in modular builds where transitive upgrades can silently change runtime behavior in later stages of the build.
However, version catalogs only influence declared versions, not resolved versions. Use them in combination with dependency locking and version alignment to enforce consistency across builds. To influence resolved versions, check out platforms.
Example
Don’t Do This
Avoid declaring versions in project.ext
, constants, or local variables:
plugins {
id("java-library")
id("com.github.ben-manes.versions").version("0.45.0")
}
val groovyVersion = "3.0.5"
dependencies {
api("org.codehaus.groovy:groovy:$groovyVersion")
api("org.codehaus.groovy:groovy-json:$groovyVersion")
api("org.codehaus.groovy:groovy-nio:$groovyVersion")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}
dependencies {
implementation("org.apache.commons:commons-lang3") {
version {
strictly("[3.8, 4.0[")
prefer("3.9")
}
}
}
plugins {
id('java-library')
id('com.github.ben-manes.versions').version('0.45.0')
}
def groovyVersion = '3.0.5'
dependencies {
api("org.codehaus.groovy:groovy:$groovyVersion")
api("org.codehaus.groovy:groovy-json:$groovyVersion")
api("org.codehaus.groovy:groovy-nio:$groovyVersion")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
implementation("org.apache.commons:commons-lang3") {
version {
strictly("[3.8, 4.0[")
prefer("3.9")
}
}
}
Avoid misusing version catalogs for unrelated concerns:
-
Don’t use them to store shared strings or non-library constants
-
Don’t overload them with arbitrary logic or plugin-specific configuration
Do This Instead
Use a centralized libs.versions.toml
file in your gradle/
directory:
[versions]
groovy = "3.0.5"
junit-jupiter = "5.10.0"
[libraries]
groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" }
groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" }
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer="3.9" } }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
[bundles]
groovy = ["groovy-core", "groovy-json", "groovy-nio"]
[plugins]
versions = { id = "com.github.ben-manes.versions", version = "0.45.0" }
plugins {
id("java-library")
alias(libs.plugins.versions)
}
dependencies {
api(libs.bundles.groovy)
testImplementation(libs.junit.jupiter)
implementation(libs.commons.lang3)
}
plugins {
id('java-library')
alias(libs.plugins.versions)
}
dependencies {
api(libs.bundles.groovy)
testImplementation(libs.junit.jupiter)
implementation(libs.commons.lang3)
}
Name Version Catalog Entries Appropriately
Consistent and descriptive names in your version catalog enhance readability and maintainability across your build scripts.
Explanation
Version catalogs provide a centralized way to manage dependencies. Adopting clear naming conventions for catalog entries ensures that developers can easily identify and use dependencies throughout the project.
The following guidelines help in naming catalog entries effectively:
-
Use dashes to separate segments: Prefer dashes (
-
) over underscores (_
) to separate different parts of the entry name.Example: For
org.apache.logging.log4j:log4j-api
, uselog4j-api
-
Derive the first segment from the project group: Use a unique identifier from the project’s group ID as the first segment.
Example: For
com.fasterxml.jackson.core:jackson-databind
, usejackson-databind
-
Omit redundant segments: If the group and artifact IDs are the same, avoid repeating them.
Example: For
io.ktor:ktor-client-core
, usektor-client-core
, notktor-ktor-client-core
-
Convert internal dashes to camelCase: If the artifact ID contains dashes, convert them to camelCase for better readability in code.
Example:
spring-boot-starter-web
becomesspringBootStarterWeb
-
Avoid implicit terms: Exclude terms that are obvious or implied in the context of your project.
Example: For
com.amazonaws:aws-java-sdk-core
, useaws-core
, notaws-javaSdkCore
-
Suffix plugin libraries with
-plugin
: When referencing a plugin as a library (not in the[plugins]
section), append-plugin
to the name.Example: For
org.owasp:dependency-check-gradle
, usedependency-check-plugin
Example
[versions]
slf4j = "2.0.13"
jackson = "2.17.1"
groovy = "3.0.5"
checkstyle = "8.37"
commonsLang = "3.9"
[libraries]
# SLF4J
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
# Jackson
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
jackson-dataformatCsv = { module = "com.fasterxml.jackson.dataformat:jackson-dataformat-csv", version.ref = "jackson" }
# Groovy bundle
groovy-core = { module = "org.codehaus.groovy:groovy", version.ref = "groovy" }
groovy-json = { module = "org.codehaus.groovy:groovy-json", version.ref = "groovy" }
groovy-nio = { module = "org.codehaus.groovy:groovy-nio", version.ref = "groovy" }
# Apache Commons Lang
commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version = { strictly = "[3.8, 4.0[", prefer = "3.9" } }
[bundles]
groovy = ["groovy-core", "groovy-json", "groovy-nio"]
[plugins]
versions = { id = "com.github.ben-manes.versions", version = "0.45.0" }
plugins {
id("java-library")
alias(libs.plugins.versions)
}
repositories {
mavenCentral()
}
dependencies {
// SLF4J
implementation(libs.slf4j.api)
// Jackson
implementation(libs.jackson.databind)
implementation(libs.jackson.dataformatCsv)
// Groovy bundle
api(libs.bundles.groovy)
// Commons Lang
implementation(libs.commons.lang3)
}
plugins {
id 'java-library'
alias(libs.plugins.versions)
}
repositories {
mavenCentral()
}
dependencies {
// SLF4J
implementation libs.slf4j.api
// Jackson
implementation libs.jackson.databind
implementation libs.jackson.dataformatCsv
// Groovy bundle
api libs.bundles.groovy
// Commons Lang
implementation libs.commons.lang3
}
Set up your Dependency Repositories in the Settings file
Declare your repositories for your plugins and dependencies in settings.gradle.kts
.
Explanation
Using settings.gradle.kts
file to declare repositories has several benefits:
-
Avoids repetition: Centralizing repository declarations eliminates the need to repeat them in each project’s
build.gradle.kts
. -
Improves debuggability: Ensures all projects resolve dependencies during resolution from the same repositories, in a consistent order.
-
Matches the build model: Repositories are not part of the project definition; they are part of global build logic, so settings is a more appropriate place for them.
While dependencyResolutionManagement.repositories is an incubating API, it is the preferred way of declaring repositories.
|
Example
Don’t Do This
You could set up repositories in individual build.gradle.kts
files with:
buildscript {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("java")
}
repositories {
mavenCentral()
}
buildscript {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("java")
}
repositories {
mavenCentral()
}
Do This Instead
Instead, you should set them up in settings.gradle.kts
like this:
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
repositories {
mavenCentral()
}
}
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
repositories {
mavenCentral()
}
}
Don’t Explicitly Depend on the Kotlin Standard Library
The Kotlin Gradle Plugin automatically adds a dependency on the Kotlin standard library (stdlib
) to each source set, so there is no need to declare it explicitly.
Explanation
The version of the standard library added is the same as the version of the Kotlin Gradle Plugin applied to the project. If your build does not require a specific or different version of the standard library, you should avoid adding it manually.
Setting the kotlin.stdlib.default.dependency property to false prevents the Kotlin plugin from automatically adding the Kotlin standard library dependency to your project. This can be useful in specific scenarios, such as when you want to manage the Kotlin standard library dependency version manually.
|
Example
Don’t Do This
plugins {
kotlin("jvm").version("2.0.21")
}
dependencies {
api(kotlin("stdlib")) (1)
}
plugins {
id("org.jetbrains.kotlin.jvm") version "2.0.21"
}
dependencies {
api("org.jetbrains.kotlin:kotlin-stdlib:2.0.21") (1)
}
1 | stdlib is explicitly depended upon: This project contains an implicit dependency on the Kotlin standard library, which is required to compile its source code. |
Do This Instead
plugins {
kotlin("jvm").version("2.0.21") (1)
}
plugins {
id("org.jetbrains.kotlin.jvm") version "2.0.21" (1)
}
1 | stdlib dependency is not included explicitly: The standard library remains available for use, and source code requiring it can be compiled without any issues. |
Avoid Redundant Dependency Declarations
Avoid declaring the same dependency multiple times, especially when it is already available transitively or through another configuration.
Explanation
Duplicating dependencies in Gradle build scripts can lead to:
-
Increased maintenance: Declaring a dependency in multiple places makes it harder to manage.
-
Unexpected behavior: Declaring the same dependency in multiple configurations (e.g.,
compileOnly
andimplementation
) can result in hard-to-diagnose classpath issues.
Example
Don’t Do This
plugins {
`java-library`
}
dependencies {
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0") (1)
}
plugins {
id 'java-library'
}
dependencies {
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0") (1)
}
1 | Redundant dependency in implementation scope. |
Do This Instead
plugins {
`java-library`
}
dependencies {
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0") (1)
}
plugins {
id 'java-library'
}
dependencies {
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.0") (1)
}
1 | Declare dependency once |