Exception handling di Java memiliki satu fitur unik yang tidak ada di C++ maupun C#: checked exception. Java memaksa programmer untuk secara eksplisit menangani atau mendeklarasikan exception tertentu — jika tidak, kode tidak akan bisa dikompilasi. Sistem ini memastikan error yang mungkin terjadi tidak diabaikan begitu saja.
Konsep Dasar: try, catch, finally
public class ContohException {
public static void main(String[] args) {
int[] nilai = {10, 20, 30};
try {
System.out.println(nilai[5]); // ArrayIndexOutOfBoundsException!
System.out.println("Baris ini tidak dieksekusi");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error: " + e.getMessage());
// Output: Error: Index 5 out of bounds for length 3
} finally {
// finally SELALU dieksekusi, baik ada exception maupun tidak
System.out.println("Blok finally selalu jalan");
// Output: Blok finally selalu jalan
}
System.out.println("Program berlanjut...");
}
}
Checked vs Unchecked Exception — Fitur Unik Java
Ini adalah perbedaan terbesar Java dengan bahasa lain:
java.lang.Throwable
├── java.lang.Error → UNCHECKED (JVM error, jangan catch)
│ ├── OutOfMemoryError
│ ├── StackOverflowError
│ └── ...
└── java.lang.Exception
├── RuntimeException → UNCHECKED (tidak wajib handle)
│ ├── NullPointerException
│ ├── ArrayIndexOutOfBoundsException
│ ├── ClassCastException
│ └── IllegalArgumentException
└── (checked exceptions) → CHECKED (wajib handle atau deklarasi)
├── IOException
├── SQLException
├── FileNotFoundException
└── ParseException
import java.io.*;
import java.text.*;
public class CheckedVsUnchecked {
// Checked exception: WAJIB deklarasi 'throws' atau tangkap dengan try/catch
// Tanpa 'throws IOException', kode ini tidak bisa dikompilasi!
static String bacaFile(String namaFile) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(namaFile));
return reader.readLine();
}
// Unchecked exception: boleh tidak dideklarasi
static int bagi(int a, int b) {
if (b == 0) throw new ArithmeticException("Pembagian dengan nol!");
return a / b;
}
public static void main(String[] args) {
// Untuk checked exception, WAJIB ada try/catch
try {
String isi = bacaFile("data.txt");
System.out.println(isi);
} catch (IOException e) {
System.out.println("File tidak ditemukan: " + e.getMessage());
}
// Untuk unchecked, try/catch bersifat opsional
System.out.println(bagi(10, 2)); // Output: 5
}
}
Filosofi Java: Checked exception untuk kondisi yang bisa diprediksi dan perlu ditangani (file tidak ada, koneksi putus). Unchecked exception untuk bug programmer (null pointer, indeks salah) yang seharusnya tidak terjadi jika kode ditulis dengan benar.
Deklarasi throws pada Method
import java.io.*;
public class DeklarasiThrows {
// Melempar checked exception ke pemanggil
static void prosesFile(String path) throws IOException, IllegalArgumentException {
if (path == null || path.isEmpty())
throw new IllegalArgumentException("Path tidak boleh kosong");
File file = new File(path);
if (!file.exists())
throw new FileNotFoundException("File tidak ditemukan: " + path);
// Proses file...
System.out.println("Memproses: " + path);
}
// Pemanggil harus handle atau teruskan lagi
static void jalankanProses(String path) throws IOException {
try {
prosesFile(path);
} catch (IllegalArgumentException e) {
System.out.println("Input tidak valid: " + e.getMessage());
// IllegalArgumentException ditangkap di sini
}
// IOException diteruskan ke pemanggil dengan 'throws IOException'
}
public static void main(String[] args) {
try {
jalankanProses("laporan.txt");
} catch (IOException e) {
System.out.println("Gagal proses file: " + e.getMessage());
}
}
}
Multi-Catch — Menangkap Beberapa Exception Sekaligus
import java.io.*;
import java.text.*;
public class MultiCatch {
static void prosesInput(String input) throws IOException, ParseException {
if (input == null)
throw new NullPointerException("Input null");
if (input.equals("io"))
throw new IOException("IO error simulasi");
if (input.equals("parse"))
throw new ParseException("Parse error simulasi", 0);
System.out.println("Berhasil: " + input);
}
public static void main(String[] args) {
String[] test = { null, "io", "parse", "ok" };
for (String input : test) {
try {
prosesInput(input);
} catch (NullPointerException e) {
// Unchecked — tidak perlu di throws declaration
System.out.println("[Null] " + e.getMessage());
} catch (IOException | ParseException e) {
// Multi-catch (Java 7+) — gabung dua exception dengan '|'
// 'e' bertipe Exception (superclass bersama)
System.out.println("[IO/Parse] " + e.getMessage());
} catch (Exception e) {
// Tangkap semua sisa
System.out.println("[Lainnya] " + e.getMessage());
}
}
// Output:
// [Null] Input null
// [IO/Parse] IO error simulasi
// [IO/Parse] Parse error simulasi
// Berhasil: ok
}
}
Aturan multi-catch: Exception dalam satu blok
catch (A | B e)tidak boleh berelasi parent-child. JikaB extends A, makacatch (A | B e)akan error kompilasi karena redundan.
try-with-resources — Pengganti Finally untuk Resource
try-with-resources (Java 7+) secara otomatis menutup resource yang mengimplementasikan AutoCloseable:
import java.io.*;
public class TryWithResources {
// Tanpa try-with-resources — verbose dan rawan lupa close
static void bacaTanpaTWR(String path) throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(path));
String baris;
while ((baris = reader.readLine()) != null) {
System.out.println(baris);
}
} finally {
if (reader != null) {
reader.close(); // Harus dipanggil manual
}
}
}
// Dengan try-with-resources — otomatis close!
static void bacaDenganTWR(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
String baris;
while ((baris = reader.readLine()) != null) {
System.out.println(baris);
}
} // reader.close() dipanggil otomatis
}
// Multiple resource — ditutup dalam urutan terbalik (reader ditutup terakhir)
static void salinFile(String src, String dst) throws IOException {
try (
BufferedReader reader = new BufferedReader(new FileReader(src));
BufferedWriter writer = new BufferedWriter(new FileWriter(dst))
) {
String baris;
while ((baris = reader.readLine()) != null) {
writer.write(baris);
writer.newLine();
}
}
System.out.println("File berhasil disalin dari " + src + " ke " + dst);
}
public static void main(String[] args) {
try {
bacaDenganTWR("input.txt");
} catch (IOException e) {
System.out.println("Gagal baca file: " + e.getMessage());
}
}
}
Custom Exception Class
// Exception khusus domain aplikasi
public class ProdukException extends RuntimeException {
// Unchecked — tidak perlu throws di method signature
private final String kodeProduk;
private final int kodeError;
public ProdukException(String kodeProduk, String pesan, int kodeError) {
super(pesan);
this.kodeProduk = kodeProduk;
this.kodeError = kodeError;
}
// Constructor dengan cause (untuk wrapping exception)
public ProdukException(String kodeProduk, String pesan, Throwable cause) {
super(pesan, cause);
this.kodeProduk = kodeProduk;
this.kodeError = 500;
}
public String getKodeProduk() { return kodeProduk; }
public int getKodeError() { return kodeError; }
}
// Checked custom exception — untuk kondisi yang bisa diprediksi
class StokHabisException extends Exception {
private final int stokTersedia;
public StokHabisException(String produk, int stokTersedia) {
super("Stok '" + produk + "' tidak mencukupi. Tersedia: " + stokTersedia);
this.stokTersedia = stokTersedia;
}
public int getStokTersedia() { return stokTersedia; }
}
// Penggunaan
class LayananProduk {
static void beliProduk(String kode, int jumlah) throws StokHabisException {
if (kode == null || kode.isEmpty())
throw new ProdukException(kode, "Kode produk tidak valid", 400);
int stok = 3; // Simulasi stok dari database
if (jumlah > stok)
throw new StokHabisException(kode, stok); // Checked exception
System.out.println("Berhasil beli " + jumlah + " unit " + kode);
}
public static void main(String[] args) {
try {
beliProduk("LAPTOP-001", 5);
} catch (ProdukException e) {
// RuntimeException — unchecked
System.out.println("Error [" + e.getKodeError() + "]: " + e.getMessage());
} catch (StokHabisException e) {
// Checked — wajib ada di try/catch atau throws
System.out.println("Stok habis! " + e.getMessage());
System.out.println("Tersedia: " + e.getStokTersedia() + " unit");
// Output: Stok habis! Stok 'LAPTOP-001' tidak mencukupi. Tersedia: 3
}
}
}
Re-throw dan Exception Chaining
import java.io.*;
import java.sql.*;
public class ExceptionChaining {
static void simpanKeDB(String data) throws SQLException {
// Simulasi error database
throw new SQLException("Koneksi database gagal");
}
static void simpanLaporan(String data) throws RuntimeException {
try {
simpanKeDB(data);
} catch (SQLException e) {
// Wrap exception: tambahkan konteks tanpa kehilangan cause
throw new RuntimeException("Gagal menyimpan laporan", e);
// e menjadi getCause() dari exception baru
}
}
public static void main(String[] args) {
try {
simpanLaporan("Data penjualan Q1");
} catch (RuntimeException e) {
System.out.println("Error: " + e.getMessage());
System.out.println("Penyebab: " + e.getCause().getMessage());
// Output:
// Error: Gagal menyimpan laporan
// Penyebab: Koneksi database gagal
}
}
}
Pertanyaan yang Sering Diajukan
Apa perbedaan checked dan unchecked exception di Java?
Checked exception (semua Exception yang bukan RuntimeException) wajib ditangani dengan try/catch atau dideklarasikan dengan throws di method signature — jika tidak, kode tidak bisa dikompilasi. Unchecked exception (RuntimeException dan turunannya) tidak wajib ditangani. Checked exception digunakan untuk kondisi yang bisa diprediksi dan perlu penanganan eksplisit (seperti IOException), sementara unchecked untuk bug programmer yang seharusnya tidak terjadi (NullPointerException, ArrayIndexOutOfBoundsException).
Kapan menggunakan checked vs unchecked exception untuk custom class?
Gunakan unchecked (extends RuntimeException) untuk exception yang menandakan bug programmer atau kondisi yang tidak bisa dipulihkan secara wajar — ini adalah pilihan mayoritas framework modern (Spring, Hibernate). Gunakan checked (extends Exception) hanya untuk kondisi yang pemanggil secara realistis bisa dan seharusnya menangani — misalnya FileNotFoundException di library file. Modern Java cenderung menghindari checked exception karena memaksa boilerplate yang mengganggu.
Apa keunggulan try-with-resources dibanding finally manual?
try-with-resources lebih aman karena: (1) close() dipanggil otomatis bahkan jika exception terjadi dalam blok try, (2) jika close() sendiri melempar exception, exception tersebut “disuppress” dan exception utama tetap dilempar (bisa diakses via getSuppressed()), (3) kode lebih ringkas dan tidak perlu null check. Dengan finally manual, ada risiko lupa memanggil close() atau exception dari close() menimpa exception utama.
Apakah boleh menangkap Exception secara langsung?
Boleh, tapi hati-hati. catch (Exception e) menangkap semua exception termasuk RuntimeException — ini bisa menyembunyikan bug programmer. Lebih baik tangkap exception spesifik. Pengecualian: di lapisan teratas aplikasi (main method, request handler) boleh menangkap Exception untuk logging sebelum crash, atau saat benar-benar ingin tangkap “semua kemungkinan error” dari kode pihak ketiga.
Kesimpulan
| Konsep | C++ | C# | Java |
|---|---|---|---|
| Checked exception | Tidak ada | Tidak ada | Ada (unik Java) |
| Resource cleanup | RAII | using statement | try-with-resources |
| Re-throw | throw; | throw; | throw e; (kehilangan trace) atau re-wrap |
| Multi-catch | catch(A|B) tidak ada, tapi catch(...) | catch (A e) when (...) | catch (A | B e) |
| Deklarasi exception | Tidak ada | Tidak ada | throws wajib untuk checked |
Artikel sebelumnya: Inheritance dan Polymorphism di Java — hierarki class di Java.
Langkah selanjutnya: File I/O di Java — cara membaca dan menulis file di Java.