Pada proyek multi-modul, deklarasi dependensi yang tersebar di banyak build.gradle.kts menjadi sumber masalah: versi tidak konsisten antar modul, duplikasi koordinat, dan kesulitan upgrade. Version catalog adalah fitur Gradle modern (stabil sejak Gradle 7.4) yang memusatkan deklarasi versi di satu file TOML. File ini menjadi sumber kebenaran tunggal untuk seluruh modul. Pada chapter ini kita akan membahas struktur file libs.versions.toml, cara mereferensikannya di build script, serta pola penggunaan [bundles] untuk mengelompokkan dependensi yang sering dipakai bersama.

Struktur File libs.versions.toml #

File gradle/libs.versions.toml terdiri atas empat section utama:

toml gradle/libs.versions.toml
[versions]
kotlin = "2.0.21"
coroutines = "1.8.1"
ktor = "2.3.12"
logback = "1.5.6"

[libraries]
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" }
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }

[bundles]
ktor = ["ktor-server-core", "ktor-server-netty"]

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

Mari kita bedah struktur di atas:

  1. [versions]: deklarasi versi tunggal. Variabel dapat dirujuk berulang via version.ref. Hal ini memastikan seluruh artefak dari grup yang sama (mis. kotlinx-coroutines-*) menggunakan versi yang konsisten.
  2. [libraries]: deklarasi artefak. Field module berisi koordinat group:name. Field version dapat berupa string literal ("1.6.3") atau version.ref = "alias" untuk merujuk versi di section [versions].
  3. [bundles]: grup artefak yang sering dipakai bersamaan. Bundle ktor di atas akan menambah ktor-server-core dan ktor-server-netty sekaligus saat direferensikan sebagai libs.bundles.ktor.
  4. [plugins]: deklarasi plugin Gradle. Plugin ini dapat diterapkan di plugins { alias(libs.plugins.kotlin.jvm) }.

Referensi di build.gradle.kts #

Setelah file libs.versions.toml dibuat, Gradle otomatis menyediakan typed accessor libs.* di semua build.gradle.kts proyek. Alias di TOML (kotlinx-coroutines-core) diubah menjadi dot notation (libs.kotlinx.coroutines.core).

kotlin build.gradle.kts — pakai catalog
plugins {
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.kotlin.serialization)
}

dependencies {
    implementation(libs.kotlinx.coroutines.core)
    implementation(libs.kotlinx.serialization.json)

    // Bundle: menambah dua artefak sekaligus
    implementation(libs.bundles.ktor)

    implementation(libs.logback.classic)
}

Demonstrasi Version Catalog #

Kode berikut mencantumkan struktur catalog dan contoh akses dari build script.

kotlin
fun main() {
    println("=== Version Catalog ===")
    println("File: gradle/libs.versions.toml")
    println()
    println("Struktur file .toml:")
    println("  [versions]   - deklarasi versi tunggal")
    println("  [libraries]  - deklarasi artefak dengan alias")
    println("  [bundles]    - grup artefak yang sering dipakai bersama")
    println("  [plugins]    - deklarasi plugin Gradle")
    println()
    println("Contoh akses dari build.gradle.kts:")
    println("  libs.coroutines.core        -> kotlinx-coroutines-core")
    println("  libs.kotlinx.serialization   -> kotlinx-serialization-json")
    println("  libs.bundles.ktor           -> ktor-server-core + ktor-server-netty")
    println("  libs.jetbrains.kotlin.jvm    -> plugin kotlin(\"jvm\")")
    println()
    println("Keuntungan:")
    println("  - Versi terpusat di satu file")
    println("  - IDE autocomplete penuh (typed accessor)")
    println("  - Berbagi antar modul tanpa duplikasi")
    println("  - Update versi cukup di satu tempat")
}

Output yang Dihasilkan:

text
=== Version Catalog ===
File: gradle/libs.versions.toml

Struktur file .toml:
  [versions]   - deklarasi versi tunggal
  [libraries]  - deklarasi artefak dengan alias
  [bundles]    - grup artefak yang sering dipakai bersama
  [plugins]    - deklarasi plugin Gradle

Contoh akses dari build.gradle.kts:
  libs.coroutines.core        -> kotlinx-coroutines-core
  libs.kotlinx.serialization   -> kotlinx-serialization-json
  libs.bundles.ktor           -> ktor-server-core + ktor-server-netty
  libs.jetbrains.kotlin.jvm    -> plugin kotlin("jvm")

Keuntungan:
  - Versi terpusat di satu file
  - IDE autocomplete penuh (typed accessor)
  - Berbagi antar modul tanpa duplikasi
  - Update versi cukup di satu tempat

Mari kita telaah kode di atas:

  1. Lokasi file: gradle/libs.versions.toml adalah path default yang dikenali Gradle. Path alternatif dapat ditentukan di settings.gradle.kts via dependencyResolutionManagement.versionCatalogs.
  2. Konversi alias ke accessor: Gradle mengonversi tanda hubung (-) dan titik (.) di alias TOML menjadi titik di accessor Kotlin. Mis. ktor-server-core → libs.ktor.server.core. Kita dapat memilih format yang lebih ringkas: ktorCore → libs.ktor.core.
  3. Keuntungan typed accessor: IntelliJ memberi autocomplete penuh. Bila alias dihapus dari TOML, build script langsung galat kompilasi (bukan runtime).

Custom Catalog Location #

Bila catalog berada di luar gradle/, definisikan secara eksplisit:

kotlin settings.gradle.kts — catalog kustom
dependencyResolutionManagement {
    versionCatalogs {
        create("libs") {
            from(files("../gradle/libs.versions.toml"))
        }
    }
}

Bundle dan Plugin #

Bundle sangat berguna saat beberapa artefak selalu dipakai bersama. Mis. untuk testing dengan MockK + JUnit:

toml Bundle untuk testing
[libraries]
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version = "5.10.0" }
mockk = { module = "io.mockk:mockk", version = "1.13.10" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }

[bundles]
testing = ["junit-jupiter", "mockk", "kotlinx-coroutines-test"]

Referensi: testImplementation(libs.bundles.testing).

Plugin dan Version #

Plugin juga dapat dirujuk via catalog. Sintaksis alias(libs.plugins.kotlin.jvm) lebih ringkas dan type-safe dibanding id("org.jetbrains.kotlin.jvm") version "2.0.21":

toml Plugin di catalog
[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kotlin build.gradle.kts — alias plugin
plugins {
    alias(libs.plugins.kotlin.jvm)
    alias(libs.plugins.kotlin.serialization)
}

Pola Versi Rich #

Catalog mendukung rich version untuk kontrol lebih:

toml Rich version di catalog
[libraries]
guava = { 
    module = "com.google.guava:guava",
    version = { 
        require = "33.0.0-jre"
        prefer = "33.1.0-jre"
    }
}
slf4j-api = {
    module = "org.slf4j:slf4j-api",
    version = { strictly = "2.0.13" }
}

Rich version sangat berguna bila kita membatasi range versi yang dapat ditarik secara transitif. Mis. strictly = "2.0.13" memaksa Gradle memilih tepat versi tersebut meski dependensi lain meminta versi berbeda.

Membandingkan dengan Pendekatan Lama #

Sebelum version catalog stabil, dua pendekatan umum dipakai di proyek Kotlin: extra properties (ext.kotlinVersion = "2.0.21") dan buildSrc (file Kotlin yang dikompilasi menjadi classpath Gradle). Version catalog menggantikan keduanya dengan keunggulan: tidak memerlukan kompilasi tambahan, mendukung autocomplete, dan tersedia lintas modul tanpa konfigurasi eksplisit.

Pendekatan buildSrc tetap berguna untuk logika build kompleks (custom task, plugin lokal), tetapi untuk deklarasi versi sederhana, version catalog adalah pilihan default.

Referensi Lanjutan #

  • Gradle version catalogs — panduan resmi Gradle untuk version catalogs di file libs.versions.toml
  • Gradle — rujukan Kotlin + Gradle untuk integrasi version catalog pada proyek Kotlin

Penutup #

Version catalog adalah praktik standar untuk proyek Gradle modern. Pemusatan versi mempermudah upgrade dan menjaga konsistensi antar modul. Pada chapter berikutnya kita akan membahas bagaimana Gradle menyelesaikan dependensi transitif dan menangani konflik versi.