Classpath bekerja baik selama proyek kecil, tetapi skala besar membawa penderitaan khas yang sudah jadi cerita lama dunia Java: dua JAR memuat class List versi berbeda, satu library hilang entah di folder mana, dan tidak ada yang tahu dependensi mana sebenarnya dipakai. Java 9 menjawab dengan Java Platform Module System (JPMS) — hasil Project Jigsaw yang distandarkan lewat JEP 261 — yang menggantikan tumpukan JAR anonim dengan module bernama yang menyatakan dependensinya sendiri. Pada Chapter ini kita akan melihat mengapa classpath bermasalah, apa itu module, dan bagaimana JVM kita saat ini sudah hidup di atas module tanpa kita sadari.
Masalah Classpath #
Tiga keluhan klasis membentuk apa yang sering disebut classpath hell:
- Class hilang saat runtime — kompilasi berhasil karena class yang dibutuhkan ada di mesin pengembang, lalu
ClassNotFoundExceptionmuncul di server produksi karena satu JAR tertinggal. Classpath hanyalah daftar lokasi; tidak ada validasi kelengkapan sebelum program berjalan. - Nama class bertabrakan — dua JAR berisi class dengan nama lengkap sama (
com.contoh.Util). JVM memuat salah satu yang ditemukan lebih dulu, dan hasilnya bergantung urutan penulisan classpath — perilaku yang sulit didiagnosis. - Dependensi tidak dinyatakan — sebuah JAR hanya berkas ZIP class; tidak ada tempat resmi untuk menulis “saya membutuhkan JAR X versi 2”. Pengguna menebak-nebak sendiri, dan tool eksternal seperti Maven hadir justru untuk mengurus kekosongan ini.
Inti masalahnya satu: classpath memperlakukan seluruh JAR sebagai satu kantong class raksasa tanpa batas dan tanpa metadata hubungan antar bagian.
Java Platform Module System #
JPMS memperkenalkan unit organisasi baru di atas package: module — kumpulan package beserta berkas deskriptor module-info.java yang menyatakan nama module, module apa yang dibutuhkan, dan package mana yang diekspor. Dengan deskriptor itu, tiga keluhan klasis mendapat jawaban struktural: launcher menolak menjalankan module bila dependensinya tidak lengkap, tabrakan nama terdeteksi lebih awal karena dua module tak boleh mengekspor package sama, dan dependensi terbaca langsung dari deklarasi.
Perjalanan fitur ini panjang: Project Jigsaw dimulai sekitar tahun 2008, bolak-balik diusulkan dan ditarik, hingga akhirnya mendarat di Java 9 tahun 2017 sebagai JEP 261. Masa inkubasi yang panjang itu menghasilkan desain yang hati-hati — kompatibilitas penuh dengan kode lama menjadi syarat mutlak yang akan kita lihat sendiri di akhir Section ini. Sebagai pengguna Java 25, kita menerima hasil akhirnya yang sudah matang: seluruh JDK kini tersusun dari module.
Anatomi paling kecilnya seperti ini:
module com.toko.kasir {
requires com.toko.produk; // module lain yang dibutuhkan
exports com.toko.kasir.ui; // package yang dibuka ke luar
}Compiler dan JVM membaca deklarasi tersebut sebagai kontrak: com.toko.kasir berhak membaca package yang diekspor com.toko.produk, dan seluruh package com.toko.kasir yang tidak diekspor bersifat internal — tidak bisa diakses module mana pun, sekalipun class-nya public. Sifat terakhir ini disebut strong encapsulation, dan inilah perbedaan paling terasa dibanding classpath yang membuka semua class public kepada siapa saja.
Module yang Sudah Menopang JVM Anda #
Berita menariknya: JDK 25 yang kita pakai sudah sepenuhnya dimodulasi — pustaka standar terpecah menjadi puluhan module. Launcher dapat menampilkan daftarnya:
java --list-modulesOutput yang Dihasilkan (dipangkas):
Module java.base menempati posisi istimewa: seluruh module otomatis membacanya tanpa perlu requires — JVM menambahkannya secara implisit karena java.lang, java.util, dan fondasi lainnya hidup di sana. Program berikut membuktikan lewat reflection ringan bahwa class sehari-hari kita berasal dari module tertentu:
public class InspeksiModul {
public static void main(String[] args) {
Module modulString = String.class.getModule();
Module modulDaftar = java.util.ArrayList.class.getModule();
Module modulSql = java.sql.Date.class.getModule();
Module modulSendiri = InspeksiModul.class.getModule();
System.out.println("String : " + modulString.getName());
System.out.println("ArrayList : " + modulDaftar.getName());
System.out.println("java.sql.Date : " + modulSql.getName());
System.out.println("class sendiri : module bernama? " + modulSendiri.isNamed());
System.out.println();
System.out.println("java.lang diekspor java.base? "
+ modulString.isExported("java.lang"));
System.out.println("jdk.internal.misc diekspor java.base? "
+ modulString.isExported("jdk.internal.misc"));
}
}Output yang Dihasilkan:
String : java.base
ArrayList : java.base
java.sql.Date : java.sql
class sendiri : module bernama? false
java.lang diekspor java.base? true
jdk.internal.misc diekspor java.base? falseMari kita bedah hasilnya:
StringdanArrayList→ java.base: class fondasi hidup di modulejava.base— module yang dibaca semua module lain secara implisit.java.sql.Date→ java.sql: JDBC dipisah ke module sendiri; class SQL tidak ikut membebani aplikasi yang tak memakainya.class sendiri : module bernama? false: kode kita yang berjalan di classpath berada di unnamed module — mode kompatibilitas yang kita bahas tuntas pada Chapter Classpath vs Module Path.isExported("jdk.internal.misc") → false: package internal JDK tidak diekspor — strong encapsulation di tempat kerjanya, melindungi kode internal platform dari pemakaian liar.
Memetakan Dependensi dengan jdeps #
Sebelum memodulasi proyek lama, kita perlu tahu kode sekarang bergantung pada module apa. Tool jdeps di JDK menganalisis bytecode dan mencetak graf dependensinya. Contoh pada module java.sql:
jdeps -s -m java.sqlOutput yang Dihasilkan:
java.sql -> java.base
java.sql -> java.logging
java.sql -> java.transaction.xa
java.sql -> java.xmlEmpat baris tersebut adalah tepat isi requires milik java.sql: JDBC membutuhkan logging, transaksi, dan XML. Informasi semacam ini yang dulu harus ditebak dari dokumentasi kini terbaca langsung dari kode — dan saat proyek Anda sendiri dianalisis, jdeps memberi peta jalan modulasi yang konkret.
Module adalah level organisasi baru, dan pintu masuk ke semuanya adalah berkas deskriptor module-info.java. Chapter berikutnya membedah anatomi berkas itu direktif demi direktif — dari requires, exports, hingga opens untuk reflection.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
InspeksiModul.java
Unduh berkaspublic class InspeksiModul {
public static void main(String[] args) {
Module modulString = String.class.getModule();
Module modulDaftar = java.util.ArrayList.class.getModule();
Module modulSql = java.sql.Date.class.getModule();
Module modulSendiri = InspeksiModul.class.getModule();
System.out.println("String : " + modulString.getName());
System.out.println("ArrayList : " + modulDaftar.getName());
System.out.println("java.sql.Date : " + modulSql.getName());
System.out.println("class sendiri : module bernama? " + modulSendiri.isNamed());
System.out.println();
System.out.println("java.lang diekspor java.base? "
+ modulString.isExported("java.lang"));
System.out.println("jdk.internal.misc diekspor java.base? "
+ modulString.isExported("jdk.internal.misc"));
}
}