Hasil kompilasi berupa banyak berkas .class tersebar di direktori out/toko, out/util, dan sebagainya — bentuk yang sulit dikirim ke pengguna lain. Java menjawab kebutuhan distribusi ini dengan format JAR (Java Archive): satu berkas yang pada dasarnya adalah arsip ZIP berisi seluruh berkas .class, resource, dan metadata bernama manifest. JDK menyertakan alat jar untuk membuat, menginspeksi, dan mengekstrak arsip tersebut tanpa perlu alat pihak ketiga. Pada chapter ini kita akan mengemas aplikasi kasir menjadi JAR library, JAR eksekusi dengan Main-Class, hingga fat jar yang memuat seluruh dependency.
Anatomi Berkas JAR #
Sebuah JAR menyimpan setiap berkas sebagai entry dengan nama yang menirukan struktur package: class kasir.Pesan disimpan sebagai kasir/Pesan.class di dalam arsip. Direktori khusus META-INF menampung metadata; berkas terpentingnya adalah META-INF/MANIFEST.MF — teks berisi atribut seperti Manifest-Version dan, untuk aplikasi eksekusi, Main-Class. Karena dasarnya ZIP, berkas JAR juga bisa dibuka dengan alat arsip biasa, tetapi perintah jar tetap cara resmi membangunnya.
Program kecil berikut akan menjadi isi JAR kita — class penyedia pesan dan class utama dengan main:
public class Pesan {
public static String sapaan(String nama) {
return "Halo, " + nama + "! Terima kasih sudah berbelanja.";
}
public static String versiAplikasi() {
return "aplikasi-kasir versi 1.0.0";
}
}public class AplikasiSelamatDatang {
public static void main(String[] args) {
System.out.println(Pesan.versiAplikasi());
System.out.println(Pesan.sapaan("Budi"));
System.out.println(Pesan.sapaan("Siti"));
System.out.println("Total hari ini: Rp209000");
}
}Output yang Dihasilkan:
aplikasi-kasir versi 1.0.0
Halo, Budi! Terima kasih sudah berbelanja.
Halo, Siti! Terima kasih sudah berbelanja.
Total hari ini: Rp209000Mari kita telaah peran tiap bagian:
- Baris 2-4 (
public static String sapaan(...)): method static mengembalikan teks sapaan; pemanggil tidak perlu membuat objekPesanlebih dulu. - Baris 7-9 (
versiAplikasi()): satu titik penyimpanan nomor versi — pola sederhana yang lazim dicantumkan juga sebagai atribut di manifest. - Baris 2 (
main(String[] args)): JVM mencari method dengan tanda tangan persis ini saat berkas class ini ditetapkan sebagaiMain-Class. - Baris 3-5 (
Pesan.versiAplikasi()danPesan.sapaan(...)): pemanggilan method static lintas class dalam package yang sama; kedua class akan dikemas bersama dalam satu JAR. - Baris 6 (
"Total hari ini: Rp" + ...): penggabungan string untuk angka — di versi berpaket nanti, baris ini memakai class bantu dari package lain.
Membuat JAR dari Hasil Kompilasi #
Skenario berikut memakai versi berpaket dari aplikasi di atas: package kasir berisi Pesan dan AplikasiSelamatDatang, package util berisi class bantu Kwitansi. Hasil kompilasi package kasir berada di direktori out, lalu kita mengemasnya:
javac -d out -cp out-util src/kasir/Pesan.java src/kasir/AplikasiSelamatDatang.java
jar --create --file kasir-lib.jar -C out .
jar --list --file kasir-lib.jarOutput yang Dihasilkan:
META-INF/
META-INF/MANIFEST.MF
kasir/
kasir/AplikasiSelamatDatang.class
kasir/Pesan.classOpsi --create memerintahkan pembuatan arsip, --file menentukan nama berkas JAR, dan -C out . berarti “sementara pindah ke direktori out, lalu masukkan semua isinya”. Tanpa -C, nama direktori out ikut tersimpan sebagai awalan entry sehingga struktur package di dalam JAR menjadi salah.
JAR seperti ini berperan sebagai library: ia tidak dieksekusi langsung, melainkan diletakkan di classpath. Cara menjalankannya sama seperti direktori di chapter sebelumnya:
java -cp kasir-lib.jar:out-util kasir.AplikasiSelamatDatangOutput yang Dihasilkan:
aplikasi-kasir versi 1.0.0
Halo, Budi! Terima kasih sudah berbelanja.
Halo, Siti! Terima kasih sudah berbelanja.
Total hari ini: Rp209.000Manifest dan Atribut Main-Class #
Manifest dibuat otomatis oleh jar, tetapi atribut Main-Class harus kita tentukan agar JAR dapat dieksekusi langsung. Opsi --main-class mengisinya sekaligus saat pembuatan:
jar --create --file kasir-app.jar --main-class kasir.AplikasiSelamatDatang -C out .
jar --extract --file kasir-app.jar META-INF/MANIFEST.MFIsi META-INF/MANIFEST.MF hasil ekstraksi:
Manifest-Version: 1.0
Created-By: 25.0.4.1 (Eclipse Adoptium)
Main-Class: kasir.AplikasiSelamatDatangTiga baris itu adalah kontrak antara JAR dan JVM: Manifest-Version menandai format manifest, Created-By mencatat alat pembuatnya, dan Main-Class memberi tahu JVM class mana yang memuat main ketika berkas JAR dijalankan.
Menjalankan dengan java -jar #
Perintah java -jar menjalankan JAR eksekusi tanpa menulis classpath:
java -jar kasir-app.jarOutput yang Dihasilkan:
aplikasi-kasir versi 1.0.0
Halo, Budi! Terima kasih sudah berbelanja.
Halo, Siti! Terima kasih sudah berbelanja.
Exception in thread "main" java.lang.NoClassDefFoundError: util/Kwitansi
at kasir.AplikasiSelamatDatang.main(AplikasiSelamatDatang.java:8)
Caused by: java.lang.ClassNotFoundException: util/KwitansiProgram berjalan dan mencetak tiga baris pertama, lalu berhenti pada baris keempat yang membutuhkan class util.Kwitansi. Inilah perilaku penting java -jar: classpath dari flag -cp diabaikan dan hanya isi JAR itu sendiri yang dipakai. Class util berada di out-util — di luar JAR — sehingga JVM gagal memuatnya tepat saat class tersebut pertama kali dibutuhkan.
Fat Jar dan Konsep Shading #
Solusi untuk JAR eksekusi mandiri adalah fat jar (juga disebut uber jar): satu arsip yang memuat class aplikasi beserta seluruh class dependency-nya. Perintah jar menerima lebih dari satu -C, sehingga dua direktori kompilasi dapat digabungkan:
jar --create --file kasir-fat.jar --main-class kasir.AplikasiSelamatDatang -C out . -C out-util .
jar --list --file kasir-fat.jar
java -jar kasir-fat.jarOutput yang Dihasilkan:
META-INF/
META-INF/MANIFEST.MF
kasir/
kasir/AplikasiSelamatDatang.class
kasir/Pesan.class
util/
util/Kwitansi.class
aplikasi-kasir versi 1.0.0
Halo, Budi! Terima kasih sudah berbelanja.
Halo, Siti! Terima kasih sudah berbelanja.
Total hari ini: Rp209.000Daftar entry kini memuat dua package sekaligus — kasir dan util — dan java -jar berhasil menyelesaikan seluruh output. Pada proyek nyata, dependency berjumlah puluhan JAR pihak ketiga; menggabungkannya dilakukan otomatis oleh plugin seperti maven-shade-plugin atau maven-assembly-plugin, bukan dengan mengetik puluhan -C manual. Kedua plugin itu juga melakukan shading: memindahkan class dependency ke package baru (misalnya com.example.shaded.jackson) agar tidak bertabrakan dengan versi library yang sama di classpath milik pengguna JAR Anda.
Menjelajahi Isi JAR #
Opsi --list menampilkan nama entry, sedangkan --describe-module memeriksa apakah JAR merupakan modul Java Platform Module System (JPMS). Untuk JAR klasik tanpa module-info.class, alat ini menurunkan nama automatic module dari nama berkas:
jar --describe-module --file kasir-fat.jarOutput yang Dihasilkan:
No module descriptor found. Derived automatic module.
kasir.fat automatic
requires java.base mandated
contains kasir
contains util
main-class kasir.AplikasiSelamatDatangNama berkas kasir-fat.jar diturunkan menjadi nama modul kasir.fat — tanda hubung diganti titik. Informasi contains menegaskan dua package di dalamnya, dan atribut main-class dibaca dari manifest. Opsi ini berguna saat Anda mulai menyusun modul eksplisit atau menelusuri kenapa sebuah JAR tidak terbaca sebagai modul.
Mengemas JAR secara manual memberi pemahaman langsung tentang manifest, entry, dan classpath. Namun aplikasi nyata membutuhkan puluhan dependency eksternal — dan mengunduhnya satu per satu sambil menjaga versinya bukan pekerjaan manusia. Chapter berikutnya memperkenalkan Maven, build tool yang mengotomatiskan kompilasi, pengujian, dan packaging dari satu berkas deskripsi proyek.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
AplikasiSelamatDatang.java
Unduh berkaspublic class AplikasiSelamatDatang {
public static void main(String[] args) {
System.out.println(Pesan.versiAplikasi());
System.out.println(Pesan.sapaan("Budi"));
System.out.println(Pesan.sapaan("Siti"));
System.out.println("Total hari ini: Rp209000");
}
}
Pesan.java
Unduh berkaspublic class Pesan {
public static String sapaan(String nama) {
return "Halo, " + nama + "! Terima kasih sudah berbelanja.";
}
public static String versiAplikasi() {
return "aplikasi-kasir versi 1.0.0";
}
}