Mengompilasi satu berkas dengan javac itu mudah; mengelola proyek nyata adalah cerita lain — puluhan berkas, direktori tersendiri untuk uji, dan pustaka pihak ketiga yang harus hadir di classpath. Dua alat menguasai wilayah itu di ekosistem Java: Maven dan Gradle. Chapter ini menjelaskan kapan keduanya dibutuhkan, menunjukkan wujud berkas konfigurasinya, lalu mengumpulkan rujukan resmi untuk pendalaman. Penutupnya berupa model sederhana yang membedakan cara keduanya menyelesaikan konflik dependensi.
Kapan Proyek Butuh Build Tool #
Tiga tanda menandai proyek yang melampaui kompilasi manual: jumlah berkas membuat perintah javac berjejalan nama, kode memakai pustaka eksternal yang harus dirangkai ke classpath secara konsisten di setiap mesin, dan ada berkas uji yang perlu dijalankan sebelum aplikasi dikemas. Alat build menyelesaikan ketiganya lewat konvensi direktori, pengunduhan dependensi otomatis dari repositori pusat, dan siklus perintah baku: kompilasi, uji, lalu kemas.
Maven: Konvensi dan POM #
Maven menata proyek lewat satu berkas XML bernama pom.xml. Identitas artefak dinyatakan dengan tiga koordinat — groupId, artifactId, version — dan struktur direktori mengikuti konvensi: kode di src/main/java, uji di src/test/java. Perintah mvn compile, mvn test, dan mvn package menjalankan siklus hidup baku tanpa kita meracik flag compiler. Berikut contoh pom.xml untuk proyek Java 25 dengan satu dependensi:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>id.example</groupId>
<artifactId>aplikasi-inventaris</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.18.2</version>
</dependency>
</dependencies>
</project>Bagian properties mengunci maven.compiler.release ke 25, dan blok dependencies menyatakan pustaka yang Maven unduh dari Maven Central saat build pertama. Panduan resmi berkas ini: Introduction to the POM di maven.apache.org/guides/introduction/introduction-to-the-pom.html.
Gradle: Skrip Build dan Plugin Java #
Gradle menata proyek lewat skrip build.gradle (DSL Groovy) atau build.gradle.kts (DSL Kotlin). Plugin java menyuntikkan task baku — gradle build, gradle test, gradle jar — dengan konvensi direktori yang sama seperti Maven, sehingga berpindah alat tidak memindahkan kode. Gradle juga memakai build bertahap dan daemon agar build berulang terasa cepat. Contoh skrip yang setara dengan POM di atas:
plugins {
id 'java'
}
group = 'id.example'
version = '1.0.0'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(25)
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.fasterxml.jackson.core:jackson-databind:2.18.2'
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.4'
}Versi JDK dinyatakan sebagai toolchain sehingga seluruh mesin build menyepakati satu versi Java. Rincian task dan ekstensi plugin Java dibaca di docs.gradle.org/current/userguide/java_plugin.html.
Resolusi Dependensi: Dua Filosofi #
Kekuatan alat build muncul saat dependensi saling bersarang dan dua jalur menuntut versi berbeda untuk pustaka yang sama. Di titik ini Maven dan Gradle berbeda filosofi: Maven memilih definisi terdekat — versi yang dinyatakan paling dekat dengan proyek menang — sedangkan Gradle memilih versi tertinggi di antara kandidat. Program berikut memodelkan kedua aturan pada satu graf kecil:
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class ResolusiDependensi {
record Modul(String nama, String versi, List<String> butuh) {}
// Graf contoh: aplikasi bergantung pada tiga modul; web dan data
// sama-sama menyeret versi umum yang lebih baru secara transitif.
static final List<Modul> GRAF = List.of(
new Modul("aplikasi", "1.0", List.of("umum:1.0", "web:2.0", "data:1.2")),
new Modul("web", "2.0", List.of("umum:1.1", "inti:1.0")),
new Modul("data", "1.2", List.of("umum:1.1")),
new Modul("umum", "1.0", List.of()),
new Modul("umum", "1.1", List.of()),
new Modul("inti", "1.0", List.of()));
public static void main(String[] args) {
System.out.println("Graf dependensi:");
for (Modul m : GRAF) {
String anak = m.butuh().isEmpty() ? " (tanpa dependensi)" : " -> " + m.butuh();
System.out.println(" " + m.nama() + " " + m.versi() + anak);
}
System.out.println();
System.out.println("Model Maven (definisi terdekat menang):");
System.out.println(" " + resolusiTerdekat());
System.out.println("Model Gradle (versi terbaru menang):");
System.out.println(" " + resolusiTerbaru());
}
// Penelusuran melebar dari dependensi langsung: nama yang terlihat
// lebih dulu menang, meniru aturan "nearest definition wins" Maven.
static Map<String, String> resolusiTerdekat() {
Map<String, String> terpilih = new TreeMap<>();
Deque<Modul> antrean = new ArrayDeque<>();
for (String ref : cari("aplikasi", "1.0").butuh()) {
antrean.add(cari(ref));
}
while (!antrean.isEmpty()) {
Modul m = antrean.poll();
if (!terpilih.containsKey(m.nama())) {
terpilih.put(m.nama(), m.versi());
for (String ref : m.butuh()) {
antrean.add(cari(ref));
}
}
}
return terpilih;
}
// Bandingkan semua versi yang muncul di graf, pilih yang tertinggi —
// meniru penyelesaian konflik versi Gradle yang memilih versi terbaru.
static Map<String, String> resolusiTerbaru() {
Map<String, String> terpilih = new TreeMap<>();
for (Modul m : GRAF) {
if (m.nama().equals("aplikasi")) {
continue;
}
terpilih.merge(m.nama(), m.versi(),
(lama, baru) -> bandingkanVersi(baru, lama) > 0 ? baru : lama);
}
return terpilih;
}
static Modul cari(String namaVersi) {
String[] bagian = namaVersi.split(":");
return cari(bagian[0], bagian[1]);
}
static Modul cari(String nama, String versi) {
for (Modul m : GRAF) {
if (m.nama().equals(nama) && m.versi().equals(versi)) {
return m;
}
}
throw new IllegalArgumentException("Modul tidak ada: " + nama + ":" + versi);
}
// Bandingkan dua string versi sederhana "mayor.minor".
static int bandingkanVersi(String a, String b) {
String[] sa = a.split("\\.");
String[] sb = b.split("\\.");
int panjang = Math.max(sa.length, sb.length);
for (int i = 0; i < panjang; i++) {
int ka = i < sa.length ? Integer.parseInt(sa[i]) : 0;
int kb = i < sb.length ? Integer.parseInt(sb[i]) : 0;
if (ka != kb) {
return Integer.compare(ka, kb);
}
}
return 0;
}
}Output yang Dihasilkan:
Graf dependensi:
aplikasi 1.0 -> [umum:1.0, web:2.0, data:1.2]
web 2.0 -> [umum:1.1, inti:1.0]
data 1.2 -> [umum:1.1]
umum 1.0 (tanpa dependensi)
umum 1.1 (tanpa dependensi)
inti 1.0 (tanpa dependensi)
Model Maven (definisi terdekat menang):
{data=1.2, inti=1.0, umum=1.0, web=2.0}
Model Gradle (versi terbaru menang):
{data=1.2, inti=1.0, umum=1.1, web=2.0}Mari kita telaah cara kerjanya:
- Baris 13-19 (
GRAF): proyekaplikasimenyatakanumum:1.0,web:2.0, dandata:1.2;webdandatasama-sama menyeretumum:1.1secara transitif — konflik klasik pada modulumum. - Baris 36-52 (
resolusiTerdekat): penelusuran melebar dari dependensi langsung membuatumum:1.0terlihat lebih dulu dan mengunci pilihan — padanan aturan nearest definition wins pada Maven. - Baris 56-66 (
resolusiTerbaru): semua versi yang muncul dibandingkan dan yang tertinggi menang, sehinggaumumterpilih1.1— padanan penyelesaian konflik versi Gradle. - Baris 21-32 (
main): kedua peta dicetak terurut nama modul; hanyaumumyang berbeda — titik yang perlu diwaspadai saat membaca berkas dependensi proyek nyata.
Kumpulan Rujukan Resmi #
Tabel berikut mengumpulkan halaman resmi yang paling sering dibuka saat bekerja dengan kedua alat:
| Rujukan | Alamat | Manfaat |
|---|---|---|
| Maven — Getting Started | maven.apache.org/guides/getting-started/index.html | Memasang Maven dan proyek pertama |
| Maven — Introduction to the POM | maven.apache.org/guides/introduction/introduction-to-the-pom.html | Membaca dan menulis pom.xml |
| Gradle — Userguide | docs.gradle.org/current/userguide/ | Manual pengguna Gradle |
| Gradle — Java Plugin | docs.gradle.org/current/userguide/java_plugin.html | Task dan konvensi proyek Java |
| Gradle — Dependency Management | docs.gradle.org/current/userguide/dependency_management_basics.html | Cara Gradle menyelesaikan dependensi |
| Maven Central | central.sonatype.com/ | Mesin pencari artefak pustaka |
Alat build menghubungkan dokumentasi dengan praktik: pustaka yang Javadoc-nya kita baca dinyatakan sebagai baris koordinat di berkas build. Begitu dependensi terpasang, pertanyaan berikutnya adalah memastikan kode tetap benar — wilayah pengujian. Chapter berikutnya mengumpulkan rujukan JUnit, Mockito, dan AssertJ beserta pola dasar pengujian tanpa framework.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
ResolusiDependensi.java
Unduh berkasimport java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
public class ResolusiDependensi {
record Modul(String nama, String versi, List<String> butuh) {}
// Graf contoh: aplikasi bergantung pada tiga modul; web dan data
// sama-sama menyeret versi umum yang lebih baru secara transitif.
static final List<Modul> GRAF = List.of(
new Modul("aplikasi", "1.0", List.of("umum:1.0", "web:2.0", "data:1.2")),
new Modul("web", "2.0", List.of("umum:1.1", "inti:1.0")),
new Modul("data", "1.2", List.of("umum:1.1")),
new Modul("umum", "1.0", List.of()),
new Modul("umum", "1.1", List.of()),
new Modul("inti", "1.0", List.of()));
public static void main(String[] args) {
System.out.println("Graf dependensi:");
for (Modul m : GRAF) {
String anak = m.butuh().isEmpty() ? " (tanpa dependensi)" : " -> " + m.butuh();
System.out.println(" " + m.nama() + " " + m.versi() + anak);
}
System.out.println();
System.out.println("Model Maven (definisi terdekat menang):");
System.out.println(" " + resolusiTerdekat());
System.out.println("Model Gradle (versi terbaru menang):");
System.out.println(" " + resolusiTerbaru());
}
// Penelusuran melebar dari dependensi langsung: nama yang terlihat
// lebih dulu menang, meniru aturan "nearest definition wins" Maven.
static Map<String, String> resolusiTerdekat() {
Map<String, String> terpilih = new TreeMap<>();
Deque<Modul> antrean = new ArrayDeque<>();
for (String ref : cari("aplikasi", "1.0").butuh()) {
antrean.add(cari(ref));
}
while (!antrean.isEmpty()) {
Modul m = antrean.poll();
if (!terpilih.containsKey(m.nama())) {
terpilih.put(m.nama(), m.versi());
for (String ref : m.butuh()) {
antrean.add(cari(ref));
}
}
}
return terpilih;
}
// Bandingkan semua versi yang muncul di graf, pilih yang tertinggi —
// meniru penyelesaian konflik versi Gradle yang memilih versi terbaru.
static Map<String, String> resolusiTerbaru() {
Map<String, String> terpilih = new TreeMap<>();
for (Modul m : GRAF) {
if (m.nama().equals("aplikasi")) {
continue;
}
terpilih.merge(m.nama(), m.versi(),
(lama, baru) -> bandingkanVersi(baru, lama) > 0 ? baru : lama);
}
return terpilih;
}
static Modul cari(String namaVersi) {
String[] bagian = namaVersi.split(":");
return cari(bagian[0], bagian[1]);
}
static Modul cari(String nama, String versi) {
for (Modul m : GRAF) {
if (m.nama().equals(nama) && m.versi().equals(versi)) {
return m;
}
}
throw new IllegalArgumentException("Modul tidak ada: " + nama + ":" + versi);
}
// Bandingkan dua string versi sederhana "mayor.minor".
static int bandingkanVersi(String a, String b) {
String[] sa = a.split("\\.");
String[] sb = b.split("\\.");
int panjang = Math.max(sa.length, sb.length);
for (int i = 0; i < panjang; i++) {
int ka = i < sa.length ? Integer.parseInt(sa[i]) : 0;
int kb = i < sb.length ? Integer.parseInt(sb[i]) : 0;
if (ka != kb) {
return Integer.compare(ka, kb);
}
}
return 0;
}
}