Belajar OOP dengan Java - Exceptions & Error Handling
Series/Belajar OOP/Episode 12
Episode 12 of 23

Belajar OOP dengan Java - Exceptions & Error Handling

Memahami hierarki Throwable beserta checked dan unchecked exception, menangani error dengan try/catch/finally, try-with-resources untuk menutup resource secara aman, multi-catch, serta merancang custom exception yang ekspresif untuk domain kalian.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Di episode-episode sebelumnya kita sering menulis throw new IllegalArgumentException ketika ada input tidak valid. Episode ini membedah dunia error handling secara penuh: bagaimana Java merepresentasikan kegagalan, kapan harus menangkap, kapan harus melempar, dan — yang paling penting — bagaimana merancang penanganan error yang tidak menelan kesalahan diam-diam.

Mengapa ini penting? Karena exception yang salah ditangani adalah sumber bug paling halus di produksi: error yang ditelan (catch kosong), exception yang di-lempar saat resource tidak ditutup, atau hierarchy yang terlalu dalam. Error handling yang baik justru membuat kegagalan terlihat jelas di tempat yang tepat.

Hierarki Throwable

Semua error di Java hidup dalam satu pohon yang berakar di Throwable:

100%
KelasSifatContoh
ErrorDi luar kendali programOutOfMemoryError, StackOverflowError
Exception (checked)Wajib ditangani compilerIOException, SQLException
RuntimeException (unchecked)Tidak wajib, bug programNullPointerException, IllegalArgumentException

Checked vs Unchecked

Pembagian ini adalah keputusan desain penting:

  • Checked exception — compiler memaksa kalian menangani (try/catch) atau menyatakan (throws). Dibuat untuk kondisi yang bisa dipulihkan dan di luar kendali program — file tidak ada, jaringan terputus.
  • Unchecked (RuntimeException) — tidak dipaksa compiler. Dibuat untuk bug yang seharusnya tidak terjadi jika kode benar — null tak terduga, indeks di luar batas, argumen tidak valid.
JavaChecked exception wajib ditangani
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
 
public class BacaFile {
    public static void main(String[] args) throws IOException {
        String isi = Files.readString(Path.of("data.txt"));
        System.out.println(isi);
    }
}

Hapus throws IOException dan kode tidak akan compile. Itu bukan kelemahan — itu sistem peringatan: compiler memaksa kalian memikirkan kegagalan sebelum terjadi.

Menangani Exception

try/catch/finally

JavaStruktur try/catch/finally
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
 
public class Main {
    public static void main(String[] args) {
        try {
            String isi = Files.readString(Path.of("data.txt"));
            System.out.println(isi);
        } catch (IOException e) {
            System.out.println("Gagal membaca file: " + e.getMessage());
        } finally {
            System.out.println("Selalu dijalankan");
        }
    }
}

Tiga blok dengan peran berbeda:

  • try — kode yang berpotensi gagal.
  • catch — penanganan ketika tipe exception tertentu muncul. finallyselalu dijalankan, baik sukses maupun gagal.

finally biasanya dipakai untuk membersihkan resource. Namun sejak Java 7 ada cara yang lebih aman: try-with-resources.

Try-With-Resources

Resource yang mengimplementasikan AutoCloseable bisa ditutup otomatis:

JavaTry-with-resources menutup otomatis
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
 
public class Main {
    public static void main(String[] args) {
        try (var reader = Files.newBufferedReader(Path.of("data.txt"), StandardCharsets.UTF_8)) {
            String baris;
            while ((baris = reader.readLine()) != null) {
                System.out.println(baris);
            }
        } catch (IOException e) {
            System.out.println("Gagal: " + e.getMessage());
        }
    }
}

Resource dideklarasikan di dalam try (...) dan ditutup otomatis — bahkan saat exception dilempar di tengah proses. Ini menghilangkan kelas bug "lupa menutup koneksi/file" yang menyebabkan kebocoran resource di produksi. BufferedReader, InputStream, Connection SQL — semua AutoCloseable.

Multi-Catch

Beberapa tipe exception dengan penanganan serupa bisa digabung dengan |:

JavaMulti-catch
try {
    int hasil = Integer.parseInt(args[0]);
    System.out.println(10 / hasil);
} catch (NumberFormatException | ArithmeticException e) {
    System.out.println("Input tidak valid: " + e.getMessage());
}

Gunakan multi-catch hanya jika penanganannya memang identik — jika berbeda, pisahkan agar masing-masing jelas.

Merancang Custom Exception

Ketika exception bawaan tidak mengekspresikan domain kalian, buat sendiri:

JavaCustom exception domain
public class SaldoTidakCukupException extends RuntimeException {
    private final double saldo;
    private final double dibutuhkan;
 
    public SaldoTidakCukupException(double saldo, double dibutuhkan) {
        super("Saldo " + saldo + " tidak cukup untuk " + dibutuhkan);
        this.saldo = saldo;
        this.dibutuhkan = dibutuhkan;
    }
 
    public double kekurangan() {
        return dibutuhkan - saldo;
    }
}

Aturan merancang custom exception:

  • Warisi RuntimeException untuk error yang merepresentasikan kontrak domain (input tidak valid, kondisi bisnis); warisi checked Exception hanya jika pemakai harus menangani.
  • Sertakan context (nilai saldo, nilai dibutuhkan) agar log berguna — exception tanpa konteks tidak membantu debugging.
  • Nama harus jelas: SaldoTidakCukupException lebih baik dari BadRequestException yang generik.

Anti-Pattern yang Wajib Dihindari

Swallowed Exception

JavaAnti-pattern: menelan error
try {
    kirimPembayaran();
} catch (Exception e) {
    // abaikan — jangan pernah begini
}

Block catch kosong adalah salah satu bug paling berbahaya: sistem terlihat bekerja padahal sedang rusak, dan tidak ada jejak di log. Minimal, log exception-nya. Idealnya, tangani atau biarkan menyebar ke lapisan yang tepat.

Catch Terlalu Umum

JavaAnti-pattern: catch Throwable
try {
    proses();
} catch (Throwable t) {
    // salah: menangkap Error juga (OOM, StackOverflow)
}

Menangkap Throwable berarti ikut menangkap Error seperti OutOfMemoryError yang seharusnya dibiarkan — menangkapnya justru menyembunyikan kegagalan sistem.

Exception untuk Kontrol Flow

Jangan pakai exception untuk logika normal (misal menunggu NumberFormatException untuk mendeteksi angka tidak valid). Gunakan validasi eksplisit dulu; exception untuk kasus luar biasa, bukan alur sehari-hari.

Tip

Pedoman praktis: biarkan error menangis keras, jangan berbisik. Jika tidak bisa menangani error dengan benar, jangan catch — biarkan menyebar ke handler global yang mencatatnya. Error yang terlihat di log jauh lebih baik daripada error yang "berhasil" disembunyikan.

Warning

Saat menangkap exception lalu melempar yang baru, selalu sertakan exception asli sebagai cause: throw new SaldoTidakCukupException(...) tidak membawa jejak aslinya, sedangkan throw new SaldoTidakCukupException(e, ...) mempertahankan stack trace. Jejak ini sangat berharga saat debugging.

Penutup

Inti yang harus dibawa pulang:

  • Hierarki: ThrowableError (di luar kendali) dan Exception (checked) / RuntimeException (unchecked).
  • Checked exception dipaksa compiler; unchecked mewakili bug yang seharusnya tidak terjadi.
  • finally untuk pembersihan, tetapi try-with-resources lebih aman untuk AutoCloseable.
  • Custom exception harus membawa konteks dan nama yang jelas.
  • Hindari swallowed exception, catch Throwable, dan exception untuk kontrol flow.

Di episode 13 selanjutnya kita akan membahas functional programming — lambdas, functional interfaces, Streams dengan map/filter/collect, Optional, dan method references. Sampai jumpa di episode 13!

Belajar OOP dengan Java - Exceptions & Error Handling | Belajar OOP