Saat program Kotlin melempar exception yang tidak tertangkap, JVM mencetak stack trace ke konsol. Stack trace adalah jejak pemanggilan method pada momen exception dilempar. Membaca stack trace dengan cepat dan tepat adalah keterampilan dasar yang menentukan seberapa cepat kita dapat menemukan akar masalah. Pada chapter ini kita akan membongkar anatomi stack trace, memahami urutan innermost ke outermost, serta cara membaca pola Caused by untuk exception berantai.

Anatomi Stack Trace #

Stack trace standar JVM terdiri atas beberapa bagian:

text
Exception in thread "main" java.lang.IllegalArgumentException: Pelanggan tidak boleh null
    at Stack_trace_demoKt.validasiPelanggan(stack_trace_demo.kt:23)
    at Stack_trace_demoKt.prosesPesanan(stack_trace_demo.kt:17)
    at Stack_trace_demoKt.main(stack_trace_demo.kt:4)
    at Stack_trace_demoKt.main(stack_trace_demo.kt:-1)

Mari kita bedah elemen-elemen di atas:

  1. Exception in thread "main": thread tempat exception dilempar. Untuk aplikasi multi-thread, ini mengidentifikasi thread mana yang bermasalah.
  2. java.lang.IllegalArgumentException: ...: tipe exception dan pesan. Pesan biasanya berisi konteks spesifik (mis. “Pelanggan tidak boleh null”).
  3. at ... .validasiPelanggan(stack_trace_demo.kt:23): satu frame stack. Formatnya adalah Class.method(file:line).
  4. Urutan frame: frame pertama adalah innermost (method yang sedang dieksekusi saat exception dilempar). Frame terakhir adalah outermost (method paling luar, biasanya main).

Urutan Innermost ke Outermost #

Banyak developer baru membaca stack trace dari atas ke bawah dan langsung melompat ke baris pertama. Ini benar, tetapi perlu diinghat bahwa baris pertama adalah tempat exception dilempar, bukan akar penyebab. Untuk memahami alur, kita perlu membaca dari atas (innermost) ke bawah (outermost) dan menelusuri rantai pemanggilan secara terbalik.

Sebaliknya, bila kita ingin tahu siapa yang memanggil method ini, kita membaca frame kedua dan seterusnya. Pola umum: frame pertama menunjukkan di mana masalah terjadi, frame kedua menunjukkan siapa yang memanggil kode bermasalah.

Demonstrasi Stack Trace #

Kode berikut menghasilkan exception dengan tiga level pemanggilan method, lalu menangkap dan mencetak stack trace secara terkontrol.

kotlin
fun main() {
    println("=== Demonstrasi Stack Trace ===")
    try {
        prosesPesanan(null)
    } catch (e: Exception) {
        println("Tertangkap di main: ${e::class.simpleName}")
        println("Pesan: ${e.message}")
        println()
        println("Stack trace (5 frame teratas):")
        e.stackTrace.take(5).forEachIndexed { i, frame ->
            println("  [$i] ${frame.className}.${frame.methodName}(${frame.fileName}:${frame.lineNumber})")
        }
    }
}

fun prosesPesanan(pelanggan: String?) {
    validasiPelanggan(pelanggan)
    println("Memproses pesanan untuk $pelanggan")
}

fun validasiPelanggan(pelanggan: String?) {
    if (pelanggan == null) {
        throw IllegalArgumentException("Pelanggan tidak boleh null")
    }
    println("Pelanggan valid: $pelanggan")
}

Output yang Dihasilkan:

text
=== Demonstrasi Stack Trace ===
Tertangkap di main: IllegalArgumentException
Pesan: Pelanggan tidak boleh null

Stack trace (5 frame teratas):
  [0] Stack_trace_demoKt.validasiPelanggan(stack_trace_demo.kt:23)
  [1] Stack_trace_demoKt.prosesPesanan(stack_trace_demo.kt:17)
  [2] Stack_trace_demoKt.main(stack_trace_demo.kt:4)
  [3] Stack_trace_demoKt.main(stack_trace_demo.kt:-1)

Mari kita telaah kode dan output di atas:

  1. Rantai pemanggilan: main memanggil prosesPesanan(null), yang memanggil validasiPelanggan(null). Stack trace mencerminkan urutan ini — frame [0] adalah validasiPelanggan (innermost), [1] adalah prosesPesanan, [2] adalah main.
  2. e.stackTrace.take(5): array StackTraceElement diakses secara terprogram. Kita memakai take(5) untuk membatasi 5 frame teratas — berguna saat log ke file untuk mengurangi noise.
  3. forEachIndexed: iterasi dengan index untuk menampilkan nomor frame. Frame paling atas ([0]) adalah method yang melempar exception.
  4. fileName:lineNumber: informasi paling penting untuk debugging. Bila kode dikompilasi dengan -g (debug info), nomor baris akurat. Tanpa -g, beberapa frame mungkin menampilkan :-1.

Pola “Caused by” #

Saat exception dibungkus (mis. throw RuntimeException("gagal", cause)), stack trace memiliki bagian Caused by:

text
java.lang.RuntimeException: Gagal memproses pesanan
    at App.prosesPesanan(App.kt:10)
    at App.main(App.kt:4)
Caused by: java.lang.IllegalArgumentException: Pelanggan null
    at App.validasi(App.kt:20)
    at App.prosesPesanan(App.kt:8)
    ... 1 more

Bagian Caused by menampilkan exception asli yang dibungkus. Untuk menemukan akar masalah, baca dari Caused by ke atas — ini biasanya adalah exception paling awal yang memicu rantai. Bila ada beberapa level wrapping, akan ada beberapa Caused by bertingkat.

Tips Membaca Stack Trace #

Beberapa aturan praktis untuk membaca stack trace dengan cepat:

  1. Baca pesan exception terlebih dahulu — biasanya berisi tipe dan deskripsi singkat.
  2. Cari baris pertama yang berasal dari kode kita sendiri — frame dari library eksternal (java.base, kotlin.stdlib) biasanya bukan akar masalah, melainkan konsekuensi dari masalah di kode kita.
  3. Telusuri rantai Caused by sampai ke exception paling awal — ini biasanya adalah akar penyebab.
  4. Periksa nomor baris di file sumber kita — buka file, lompat ke baris tersebut, dan baca kode sekitarnya.
  5. Bila ada beberapa thread, pastikan membaca stack trace thread yang benar (lihat label Exception in thread "...").

Referensi Lanjutan #

Penutup #

Kemampuan membaca stack trace adalah keterampilan yang berkembang dengan latihan. Setiap stack trace yang kita analisis membangun intuisi tentang pola error umum (null pointer, index out of bounds, class cast) dan lokasi tipikalnya. Pada chapter berikutnya kita akan naik ke alat yang lebih interaktif: IDE debugger untuk menjeda eksekusi dan memeriksa state program saat runtime.