Saat kita menambah satu dependensi di build.gradle.kts, Gradle tidak hanya mengunduh artefak tersebut tetapi juga seluruh pohon dependensi transitifnya. Sebuah library kecil dapat menarik puluhan artefak lain. Konflik versi antara dependensi transitif adalah sumber masalah umum: dua library membutuhkan versi berbeda dari guava atau slf4j-api, dan Gradle harus memilih satu. Pada chapter ini kita akan membahas strategi resolusi bawaan Gradle, cara mengontrol via strict, exclude, dan constraints, serta perintah untuk memeriksa pohon dependensi.

Dependensi Transitif #

Setiap dependensi yang dideklarasikan di build.gradle.kts membawa dependensi transitif dari POM-nya. Misalnya, deklarasi ktor-server-core akan menarik ktor-http, ktor-utils, kotlinx-coroutines-core, dan kotlin-stdlib. Pohon dependensi dapat dilihat dengan:

bash
# Tampilkan pohon dependensi lengkap
./gradlew dependencies

# Filter per configuration
./gradlew dependencies --configuration runtimeClasspath

# Lihat detail satu dependensi (siapa yang membawanya, ke versi apa)
./gradlew dependencyInsight --dependency kotlinx-coroutines-core

Strategi Resolusi Konflik #

Saat dua dependensi memerlukan versi berbeda dari artefak yang sama, Gradle memilih berdasarkan strategi default: versi tertinggi menang (highest version wins). Misalnya bila library-A:1.0 membawa library-B:1.5 dan library-C:1.0 membawa library-B:2.0, Gradle memilih library-B:2.0.

Strategi lain yang dapat dipakai:

StrategiSintaksisKapan dipakai
Strict versionversion { strictly("1.5") }Bila library-B hanya kompatibel dengan satu versi
Required versionversion { require("1.5") }Preferensi versi (bisa dinaikkan bila dependensi lain butuh lebih tinggi)
Prefer versionversion { prefer("1.5") }Default bila tidak ada konflik
Reject versionversion { reject("[1.0,1.5[") }Versi yang diketahui bermasalah
excludeexclude(group = "...", module = "...")Menghapus dependensi transitif tertentu
constraintsconstraints { implementation("group:name:1.5") }Membatasi range versi transitif

Contoh Konflik #

kotlin build.gradle.kts — konflik dependensi
dependencies {
    implementation("com.library-a:library-a:1.0")  // membawa com.example:common:1.5
    implementation("com.library-c:library-c:1.0")  // membawa com.example:common:2.0

    // Konflik: dua versi common. Default: versi 2.0 dipilih.

    // Paksa versi 1.5 bila library-a tidak kompatibel dengan 2.0
    implementation("com.example:common") {
        version { strictly("1.5") }
    }

    // Atau hapus dependensi transitif tertentu
    implementation("com.library-c:library-c:1.0") {
        exclude(group = "com.example", module = "common")
    }

    // Atau batasi range versi yang diperbolehkan
    constraints {
        implementation("com.example:common:1.5") {
            because("library-a hanya kompatibel dengan 1.5")
        }
    }
}

Demonstrasi Resolusi #

Kode berikut mencantumkan skenario konflik dependensi dan strategi penyelesaiannya.

kotlin
fun main() {
    println("=== Resolusi Dependensi ===")
    println()
    println("Skenario dependensi transitif:")
    println("  app -> library-A:1.0")
    println("  library-A:1.0 -> library-B:1.5")
    println("  app -> library-C:1.0")
    println("  library-C:1.0 -> library-B:2.0   (konflik!)")
    println()
    println("Strategi penyelesaian konflik:")
    println("  Default        : highest version wins (library-B:2.0 dipilih)")
    println("  Strict version : paksa versi spesifik meski konflik")
    println("  exclude        : hapus dependensi transitif tertentu")
    println("  constraints    : batasi range versi yang diperbolehkan")
    println()
    println("Perintah pemeriksaan:")
    println("  ./gradlew dependencies            -> tree lengkap")
    println("  ./gradlew dependencies --configuration runtimeClasspath")
    println("  ./gradlew dependencyInsight --dependency library-B")
    println()
    println("Saat konflik tidak dapat diselesaikan otomatis, Gradle menghentikan build")
    println("dengan pesan 'Conflict(s) found'. Bila ini terjadi, gunakan strict version")
    println("atau exclude untuk menyelesaikan secara eksplisit.")
}

Output yang Dihasilkan:

text
=== Resolusi Dependensi ===

Skenario dependensi transitif:
  app -> library-A:1.0
  library-A:1.0 -> library-B:1.5
  app -> library-C:1.0
  library-C:1.0 -> library-B:2.0   (konflik!)

Strategi penyelesaian konflik:
  Default        : highest version wins (library-B:2.0 dipilih)
  Strict version : paksa versi spesifik meski konflik
  exclude        : hapus dependensi transitif tertentu
  constraints    : batasi range versi yang diperbolehkan

Perintah pemeriksaan:
  ./gradlew dependencies            -> tree lengkap
  ./gradlew dependencies --configuration runtimeClasspath
  ./gradlew dependencyInsight --dependency library-B

Saat konflik tidak dapat diselesaikan otomatis, Gradle menghentikan build
dengan pesan 'Conflict(s) found'. Bila ini terjadi, gunakan strict version
atau exclude untuk menyelesaikan secara eksplisit.

Mari kita telaah kode di atas:

  1. Skenario konflik: tiga dependensi membentuk diamond — app memakai dua library yang sama-sama membawa library-B dengan versi berbeda. Inilah pola konflik paling umum di proyek nyata.
  2. Strategi default: highest version wins meminimalkan risiko missing API, namun dapat memecahkan kompatibilitas biner bila library-B breaking change antara 1.5 dan 2.0.
  3. exclude: menghapus dependensi tertentu dari grafik resolusi. Berguna bila sebuah library membawa dependensi yang sudah tidak relevan atau bermasalah (mis. commons-logging saat proyek sudah pakai jcl-over-slf4j).
  4. constraints: membatasi range versi transitif tanpa menambah dependensi langsung. Cocok bila kita ingin memastikan seluruh dependensi transitif memakai versi aman (mis. >=1.5,<2.0).

dependencyInsight #

Perintah dependencyInsight adalah alat investigasi paling kuat:

text
$ ./gradlew dependencyInsight --dependency kotlinx-coroutines-core

Project :app
------------------------------------------------------------
kotlinx-coroutines-core:1.8.1 (selected)
   variant: runtime
   selected by:
      - app (constraints) require 1.8.1
   requested by:
      - app:1.0 via libs.coroutines.core
      - io.ktor:ktor-server-core:2.3.12 by artifact
      - io.ktor:ktor-utils:2.3.12 by artifact

Output menampilkan versi yang dipilih, siapa yang meminta, dan lewat jalur apa. Informasi ini menjawab pertanyaan “mengapa versi X yang dipilih?”

Konflik Tidak Terselesaikan #

Bila dua dependensi memerlukan versi strict yang berbeda, Gradle menghentikan build:

text
> Conflict(s) found for the following module(s):
    - com.example:common between versions 1.5 and 2.0
  Run with --scan to get more insight.

Saat pesan ini muncul, gunakan dependencyInsight untuk mengetahui siapa yang meminta tiap versi, lalu pilih strategi: upgrade library-a, downgrade library-c, atau ganti library yang bermasalah dengan alternatif.

Referensi Lanjutan #

  • Gradle User Guide — buku panduan Gradle untuk dependency resolution, conflict resolution, dan penguncian versi
  • Gradle version catalogs — panduan version catalog sebagai mekanis pusat deklarasi versi yang konsisten

Penutup #

Resolusi dependensi transitif adalah mekanisme kuat tetapi berisiko bila tidak dikelola. Pemeriksaan rutin via dependencies dan dependencyInsight, plus pinning versi via catalog, menjaga proyek tetap sehat. Pada chapter berikutnya kita akan beralih ke Maven — alternatif build tool yang lebih lama tetapi masih dominan di banyak organisasi.