Di Maven kita mendeklarasikan dependency dengan scope; di Gradle kita mendeklarasikannya ke dalam configuration — wadah bernama yang menentukan ke classpath mana sebuah dependency dibawa. Filosofinya berbeda secara halus namun berdampak besar: configuration bukan sekadar “kapan library dipakai”, melainkan “apa yang boleh terlihat oleh modul lain”. Ditambah version catalog untuk merapikan ratusan nomor versi, Gradle menawarkan pengelolaan dependency yang presisi untuk build sekecil aplikasi CLI maupun sebesar monorepo ratusan modul. Pada chapter ini kita akan menulis deklarasi dependency, membedakan implementation dari api, dan menyusun version catalog libs.versions.toml.
Deklarasi Dependency dan Konfigurasi #
Dependency dideklarasikan pada blok dependencies di build.gradle.kts:
dependencies {
implementation("com.fasterxml.jackson.core:jackson-databind:2.17.1")
implementation(project(":lib-internal"))
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
runtimeOnly("org.postgresql:postgresql:42.7.3")
}Setiap baris memuat koordinat group:artifact:version — format yang sama dengan Maven karena Gradle membaca repositori yang kompatibel, termasuk Maven Central. Nama configuration di depan (implementation, testImplementation, runtimeOnly) yang menentukan nasibnya. Bentuk project(":lib-internal") menyatakan dependency antar modul dalam multi-project build, diresolusikan lewat output task masing-masing modul, bukan lewat JAR terbitan.
implementation versus api #
Perbedaan paling penting dalam daftar Gradle adalah antara implementation dan api. Keduanya sama-sama membawa dependency ke classpath runtime; bedanya pada classpath kompilasi modul konsumen. Program berikut memodelkan dua classpath proyek app yang mengonsumsi modul lib-internal:
import java.util.Map;
import java.util.TreeMap;
public class KonfigurasiDistribusi {
// Dependency milik modul 'lib-internal' beserta konfigurasinya.
private static final String[][] DEPS_LIB_INTERNAL = {
{"jackson-databind", "api"},
{"logback-classic", "implementation"}
};
public static void main(String[] args) {
// Bagian 1: version catalog sebagaimana tersimpan di libs.versions.toml
Map<String, String> katalog = new TreeMap<>();
katalog.put("jackson-databind", "com.fasterxml.jackson.core:jackson-databind:2.17.1");
katalog.put("logback-classic", "ch.qos.logback:logback-classic:1.5.6");
System.out.println("== version catalog (gradle/libs.versions.toml) ==");
for (Map.Entry<String, String> entri : katalog.entrySet()) {
System.out.printf("%-16s -> %s%n", entri.getKey(), entri.getValue());
}
System.out.println();
// Bagian 2: resolusi classpath proyek 'app' yang bergantung pada lib-internal
System.out.println("proyek 'app' memakai implementation(project(\":lib-internal\"))");
System.out.println();
System.out.println("compileClasspath 'app' (saat menulis kode):");
System.out.println(" lib-internal");
for (String[] dep : DEPS_LIB_INTERNAL) {
if (dep[1].equals("api")) {
System.out.println(" " + dep[0] + " (via api lib-internal)");
}
}
System.out.println();
System.out.println("runtimeClasspath 'app' (saat program berjalan):");
System.out.println(" lib-internal");
for (String[] dep : DEPS_LIB_INTERNAL) {
System.out.println(" " + dep[0]);
}
}
}Output yang Dihasilkan:
== version catalog (gradle/libs.versions.toml) ==
jackson-databind -> com.fasterxml.jackson.core:jackson-databind:2.17.1
logback-classic -> ch.qos.logback:logback-classic:1.5.6
proyek 'app' memakai implementation(project(":lib-internal"))
compileClasspath 'app' (saat menulis kode):
lib-internal
jackson-databind (via api lib-internal)
runtimeClasspath 'app' (saat program berjalan):
lib-internal
jackson-databind
logback-classicMari kita bedah hasil resolusinya:
compileClasspath 'app': hanya memuatlib-internaldanjackson-databind— dependency yang dideklarasikanapiolehlib-internalikut terlihat oleh konsumen;logback-classicsama sekali tidak muncul karena dideklarasikanimplementation.runtimeClasspath 'app': memuat ketiganya — saat berjalan, seluruh dependency transitif hadir tanpa memandang configuration asalnya.- Implikasi kompilasi: karena
logback-classictak terlihat, kodeapptidak bisa memanggil class logging milik Logback — modul terlindung dari detail internal pustaka yang dipakainya. - Implikasi kecepatan: bila
lib-internalkelak mengganti implementasi logging-nya,apptidak perlu dikompilasi ulang karena classpath kompilasinya tidak berubah — inilah alasan Gradle merekomendasikanimplementationsebagai pilihan bawaan.
| Konfigurasi | Terlihat di kompilasi konsumen | Hadir saat runtime | Kegunaan tipikal |
|---|---|---|---|
api | ya | ya | tipe library muncul di method publik modul |
implementation | tidak | ya | detail internal modul |
testImplementation | tidak | hanya saat uji | JUnit, Mockito |
compileOnly | ya | tidak | annotation processor |
runtimeOnly | tidak | ya | driver JDBC, implementasi logging |
Version Catalog #
Proyek multi-modul dengan puluhan dependency membuat nomor versi tersebar. Version catalog memusatkan seluruhnya dalam satu berkas TOML — gradle/libs.versions.toml:
[versions]
jackson = "2.17.1"
logback = "1.5.6"
[libraries]
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }Berkas ini dibaca Gradle pada tahap initialization dan diekspos ke build script sebagai objek libs, dengan alias yang dipisah tanda hubung menjadi akses bertitik:
dependencies {
implementation(libs.jackson.databind)
implementation(libs.logback.classic)
}Manfaatnya ganda. Pertama, satu versi untuk semua modul — jackson = "2.17.1" diabadikan sekali dan dipakai lintas app, lib-internal, dan modul lain. Kedua, versi dapat dinaikkan dalam satu commit yang mudah direviu, jauh lebih aman daripada mengganti angka di dua belas build script berbeda.
Menyelidiki dan Mengarahkan Resolusi #
Sama seperti Maven, Gradle menyediakan perintah diagnostik untuk membaca hasil resolusi:
gradle dependencies
gradle dependencyInsight --dependency jackson-databind --configuration runtimeClasspathPerintah dependencies menampilkan seluruh pohon per configuration, sedangkan dependencyInsight menelusuri satu artifact — jawaban cepat untuk pertanyaan “versi berapa yang terpilih dan dari cabang mana asalnya”. Ketika hasil resolusi perlu diarahkan, Gradle membuka resolution strategy: aturan pada level configuration untuk memaksa versi, menolak versi tertentu, atau mensubstitusi modul — alat utama saat menghadapi konflik versi yang kita bahas tuntas pada chapter Konflik Dependency.
Deklarasi dan versi sudah tertata; satu potong lagi membuat build Gradle terasa ringkas dalam praktik: plugin yang otomatis mendaftarkan task compileJava, test, dan jar begitu dua baris ditulis. Chapter berikutnya membongkar cara kerja plugin Gradle.
Referensi #
- Gradle: Declaring Dependencies
- Gradle: Dependency Management Terminology
- Gradle: Version Catalogs
- Gradle: Viewing and Debugging Dependencies
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
KonfigurasiDistribusi.java
Unduh berkasimport java.util.Map;
import java.util.TreeMap;
public class KonfigurasiDistribusi {
// Dependency milik modul 'lib-internal' beserta konfigurasinya.
private static final String[][] DEPS_LIB_INTERNAL = {
{"jackson-databind", "api"},
{"logback-classic", "implementation"}
};
public static void main(String[] args) {
// Bagian 1: version catalog sebagaimana tersimpan di libs.versions.toml
Map<String, String> katalog = new TreeMap<>();
katalog.put("jackson-databind", "com.fasterxml.jackson.core:jackson-databind:2.17.1");
katalog.put("logback-classic", "ch.qos.logback:logback-classic:1.5.6");
System.out.println("== version catalog (gradle/libs.versions.toml) ==");
for (Map.Entry<String, String> entri : katalog.entrySet()) {
System.out.printf("%-16s -> %s%n", entri.getKey(), entri.getValue());
}
System.out.println();
// Bagian 2: resolusi classpath proyek 'app' yang bergantung pada lib-internal
System.out.println("proyek 'app' memakai implementation(project(\":lib-internal\"))");
System.out.println();
System.out.println("compileClasspath 'app' (saat menulis kode):");
System.out.println(" lib-internal");
for (String[] dep : DEPS_LIB_INTERNAL) {
if (dep[1].equals("api")) {
System.out.println(" " + dep[0] + " (via api lib-internal)");
}
}
System.out.println();
System.out.println("runtimeClasspath 'app' (saat program berjalan):");
System.out.println(" lib-internal");
for (String[] dep : DEPS_LIB_INTERNAL) {
System.out.println(" " + dep[0]);
}
}
}