Pada kebanyakan bahasa, throw adalah pernyataan biasa yang berhenti mengeksekusi blok saat ini. Kotlin melangkah lebih jauh: throw adalah expression bertipe Nothing. Tipe ini memiliki peran khusus dalam sistem tipe Kotlin — ia menandakan ekspresi yang tidak pernah menghasilkan nilai karena aliran kontrol berhenti di titik tersebut. Pada chapter ini kita akan memanfaatkan sifat expression throw pada elvis operator, cabang when, dan function dengan return type non-void.

throw sebagai Expression #

Karena throw bertipe Nothing, ia dapat muncul di tempat-tempat yang mengharapkan nilai. Tipe Nothing adalah subtype dari semua tipe lain di Kotlin, sehingga kompilator mengizinkan throw muncul di cabang when, di sisi kanan elvis, atau di akhir function yang seharusnya mengembalikan Int, String, atau tipe apa pun.

kotlin throw sebagai nilai di elvis
fun namaWajib(maybe: String?): String {
    val nama: String = maybe ?: throw IllegalStateException("nama wajib ada")
    return nama.uppercase()
}

Tanpa sifat expression, kita perlu menulis if (maybe == null) throw ... else return maybe.uppercase(). Sifat expression throw membuat kode pemulihan nilai null menjadi satu baris yang ringkas.

Memadukan throw dengan Elvis #

Elvis operator ?: mengembalikan operand kiri bila non-null, dan operand kanan bila kiri null. Bila operand kanan adalah throw, kompilator mengenali bahwa kasus null akan berhenti di titik tersebut, sehingga tipe hasil keseluruhan tetap non-null.

kotlin
// Demo throw sebagai expression bertipe Nothing, dipakai dalam elvis dan when.

fun cariPengguna(id: Int): String {
    if (id <= 0) {
        // throw adalah expression bertipe Nothing; cocok di cabang when yang butuh tipe sama
        throw IllegalArgumentException("id harus positif, diterima: $id")
    }
    return "Pengguna-$id"
}

fun namaAtauGagal(maybe: String?): String {
    // elvis dengan throw: bila maybe null, lempar exception
    val nama: String = maybe ?: throw IllegalStateException("nama wajib ada")
    return nama.uppercase()
}

fun bagiAman(a: Int, b: Int): Int {
    // bila b == 0, gunakan throw sebagai salah satu cabang when
    return when {
        b == 0 -> throw ArithmeticException("pembagi tidak boleh nol")
        else -> a / b
    }
}

fun validasiUmur(umur: Int): String {
    require(umur >= 0) { "umur tidak boleh negatif: $umur" }
    require(umur <= 150) { "umur di luar batas wajar: $umur" }
    return when {
        umur < 12 -> "anak"
        umur < 18 -> "remaja"
        umur < 60 -> "dewasa"
        else -> "lansia"
    }
}

fun prosesPerintah(perintah: String): String {
    // throw di cabang else when: kompilator tahu semua cabang menghasilkan String
    return when (perintah) {
        "halo" -> "Halo juga!"
        "waktu" -> "2026-01-15"
        else -> throw UnsupportedOperationException("perintah tidak dikenal: $perintah")
    }
}

fun tidakPernahKembali(): Int {
    // fungsi dengan return type Int, namun throw menjamin tidak pernah benar-benar return
    throw IllegalStateException("fungsi ini selalu melempar")
}

fun main() {
    println("=== cariPengguna ===")
    println(cariPengguna(7))

    println()
    println("=== namaAtauGagal ===")
    println(namaAtauGagal("Budi"))
    // namaAtauGagal(null)  // uncomment untuk melihat IllegalStateException

    println()
    println("=== bagiAman ===")
    println("10 / 2 = ${bagiAman(10, 2)}")
    try {
        bagiAman(5, 0)
    } catch (e: ArithmeticException) {
        println("tangkap: ${e.message}")
    }

    println()
    println("=== validasiUmur dengan require ===")
    for (u in listOf(8, 17, 40, 70)) {
        println("umur $u -> ${validasiUmur(u)}")
    }
    try {
        validasiUmur(-1)
    } catch (e: IllegalArgumentException) {
        println("tangkap: ${e.message}")
    }

    println()
    println("=== prosesPerintah dengan throw di else ===")
    println(prosesPerintah("halo"))
    println(prosesPerintah("waktu"))
    try {
        prosesPerintah("hapus")
    } catch (e: UnsupportedOperationException) {
        println("tangkap: ${e.message}")
    }
}

Output yang Dihasilkan:

text
=== cariPengguna ===
Pengguna-7

=== namaAtauGagal ===
BUDI

=== bagiAman ===
10 / 2 = 5
tangkap: pembagi tidak boleh nol

=== validasiUmur dengan require ===
umur 8 -> anak
umur 17 -> remaja
umur 40 -> dewasa
umur 70 -> lansia
tangkap: umur tidak boleh negatif: -1

=== prosesPerintah dengan throw di else ===
Halo juga!
2026-01-15
tangkap: perintah tidak dikenal: hapus

Mari kita perhatikan bagian-bagian penting dari kode di atas:

  1. cariPengguna: Pemeriksaan if (id <= 0) throw ... diletakkan sebelum return. Karena throw bertipe Nothing, kompilator mengizinkan return "Pengguna-$id" setelahnya, dengan pemahaman bahwa cabang throw tidak akan pernah mencapai return.
  2. namaAtauGagal: Elvis ?: throw membuat nama bertipe String non-null. Bila maybe null, throw dieksekusi dan function berhenti; bila tidak, nama diisi nilai non-null.
  3. bagiAman: Cabang b == 0 -> throw ArithmeticException(...) dan cabang else -> a / b sama-sama menghasilkan Int (cabang throw dianggap menghasilkan Nothing, yang merupakan subtype dari Int).
  4. validasiUmur: Function standard library require(condisi) { pesan } melempar IllegalArgumentException bila kondisi false. Bentuk ringkas ini menggantikan pola manual if (!kondisi) throw ....
  5. prosesPerintah: Cabang else -> throw UnsupportedOperationException(...) menjamin when menghasilkan String (kompilator menganggap Nothing kompatibel). Function mengembalikan String tanpa perlu operator !! atau fallback eksplisit.

Tipe Nothing dan Implikasinya #

Nothing adalah tipe khusus yang tidak memiliki instance. Function yang return type-nya Nothing dijamin tidak pernah kembali secara normal — entah melempar exception atau mengulang selamanya (misalnya loop tak hingga). Karena Nothing adalah subtype dari setiap tipe lain, sebuah Nothing dapat dipakai sebagai pengganti tipe apa pun dalam konteks yang mengharapkan nilai.

kotlin Function dengan return type Nothing
fun gagal(pesan: String): Nothing {
    throw IllegalStateException(pesan)
}

fun cari(id: Int): String {
    if (id <= 0) return gagal("id tidak valid")   // OK: Nothing kompatibel dengan String
    return "Pengguna-$id"
}

Pada contoh di atas, function gagal mendeklarasikan return type Nothing. Saat dipanggil di cari, kompilator mengizinkan return gagal(...) seolah-olah gagal mengembalikan String. Hal ini dimungkinkan karena Nothing adalah subtype universal.

Helper require dan check #

Standard library Kotlin menyediakan dua helper yang membungkus pola if (!kondisi) throw:

kotlin require dan check
fun setHarga(harga: Int) {
    require(harga >= 0) { "harga tidak boleh negatif: $harga" }   // IllegalArgumentException
    check(harga < 1_000_000) { "harga di atas batas wajar" }     // IllegalStateException
}

fun main() {
    try { setHarga(-50) } catch (e: IllegalArgumentException) { println(e.message) }
    try { setHarga(2_000_000) } catch (e: IllegalStateException) { println(e.message) }
}

Helper require dipakai untuk validasi argumen input (melempar IllegalArgumentException), sementara check dipakai untuk memverifikasi state internal (melempar IllegalStateException). Keduanya menerima lambda untuk pesan agar evaluasi pesan hanya terjadi bila kondisi false.

Referensi Lanjutan #

  • Exceptions — bagian throw sebagai expression yang menghasilkan Nothing

Penutup #

Karena throw adalah expression bertipe Nothing, ia dapat diandalkan untuk mempersingkat kode pemulihan nilai null, validasi argumen, dan cabang when yang tidak mungkin terjadi. Helper require dan check melengkapi pola ini untuk validasi argumen dan state. Berikutnya, kita akan mendefinisikan jenis exception kita sendiri melalui custom exception yang mewarisi Exception atau RuntimeException.