Java membagi exception menjadi checked (wajib deklarasi throws atau tangkap) dan unchecked (bebas). Kotlin tidak memiliki konsep checked exception — semua exception diperlakukan sebagai unchecked. Saat kode Kotlin memanggil method Java yang melempar checked exception, Kotlin tidak memaksa kita mendeklarasikan throws atau membungkus dalam try-catch. Namun exception tersebut tetap dapat dilempar saat runtime, sehingga try-catch tetap berfungsi normal. Bab ini membahas perilaku ini, peran @Throws untuk konsumsi Java, dan pola multi-catch Kotlin.
Perilaku Default: Checked di Java, Unchecked di Kotlin #
Saat method Java seperti FileInputStream(String) yang dideklarasikan throws FileNotFoundException dipanggil dari Kotlin, kompilator Kotlin tidak memaksa penanganan. Kode berikut valid tanpa throws atau try-catch:
fun bacaBaris(path: String): String {
FileInputStream(path).use { it.read().toChar().toString() }
}Di Java, kode di atas tidak akan dikompilasi tanpa throws IOException atau try-catch. Kotlin mengabaikan aturan checked exception karena dianggap terlalu kaku dan mendorong boilerplate yang tidak menambah keamanan nyata.
| Sumber Exception | Java | Kotlin |
|---|---|---|
IOException (checked di Java) | Wajib throws/catch | Tidak wajib, bebas |
ParseException (checked di Java) | Wajib throws/catch | Tidak wajib, bebas |
RuntimeException (unchecked) | Bebas | Bebas |
OutOfMemoryError (Error) | Bebas | Bebas |
Meski tidak wajib, exception tetap dilempar saat runtime. Kode Kotlin dapat menangkapnya dengan try-catch kapan pun diperlukan.
Contoh Lengkap: Tangkap Exception Java dari Kotlin #
Kode berikut memanggil method Java berisiko (FileInputStream, SimpleDateFormat.parse) tanpa deklarasi throws, lalu menangkap exception-nya secara terstruktur:
import java.io.FileInputStream
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
// Demonstrasi checked exceptions interoperability:
// - Java checked exceptions (IOException, ParseException) TIDAK diwajibkan
// deklarasi throws di Kotlin.
// - Kotlin TETAP dapat menangkapnya dengan try-catch.
// - @Throws untuk kompatibilitas Java: bila kode Java akan memanggil Kotlin
// dan ingin melihat checked exception pada signature-nya.
// 1. Fungsi Kotlin yang membaca berkas tanpa deklarasi 'throws IOException'
fun bacaBarisPertama(path: String): String {
FileInputStream(path).use { fis ->
val byte = fis.read()
return if (byte == -1) "" else byte.toChar().toString()
}
}
// 2. Fungsi Kotlin yang memparse tanggal tanpa deklarasi 'throws ParseException'
fun parseTanggalSimple(tanggalStr: String): Date {
val fmt = SimpleDateFormat("yyyy-MM-dd")
return fmt.parse(tanggalStr)
}
// 3. @Throws untuk konsumsi Java: signature Kotlin memiliki throws IOException
@Throws(IOException::class)
fun bacaBarisPertamaUntukJava(path: String): String {
FileInputStream(path).use { fis ->
val byte = fis.read()
return if (byte == -1) "" else byte.toChar().toString()
}
}
fun main() {
// 1. Tangkap IOException dari FileInputStream (checked di Java,Unchecked di Kotlin)
println("=== Tangkap IOException ===")
try {
val isi = bacaBarisPertama("/tmp/berkas_tidak_ada.txt")
println("isi: $isi")
} catch (e: IOException) {
println("Tangkap IOException: ${e.javaClass.simpleName}")
println(" pesan: ${e.message}")
}
// 2. Tangkap ParseException dari SimpleDateFormat
println()
println("=== Tangkap ParseException ===")
val inputBerisiko = listOf("2026-01-15", "bukan-tanggal", "2026/01/15")
for (input in inputBerisiko) {
try {
val tanggal = parseTanggalSimple(input)
println("input: ${input.padEnd(20)} -> sukses: $tanggal")
} catch (e: java.text.ParseException) {
println("input: ${input.padEnd(20)} -> ParseException: ${e.message}")
}
}
// 3. Exception bertele (chained) tetap dapat ditangkap
println()
println("=== Tangkap ClassCastException ===")
try {
val apa: Any = "ini string"
val angka: Int = apa as Int // melempar ClassCastException
} catch (e: ClassCastException) {
println("Tangkap: ${e.javaClass.simpleName}")
println(" pesan: ${e.message}")
}
// 4. Multiple exception dalam satu catch via '|'
println()
println("=== Multi-catch ===")
val data = listOf("2026-01-15", "tidak-valid", "/tmp/file_lagi_tidak_ada")
for (d in data) {
try {
if (d.contains("-") && d.length == 10) {
val t = parseTanggalSimple(d)
println("$d -> sukses parse: $t")
} else if (d.startsWith("/")) {
val isi = bacaBarisPertama(d)
println("$d -> sukses baca: $isi")
} else {
println("$d -> format tidak dikenali")
}
} catch (e: IOException) {
println("$d -> IOException: ${e.message}")
} catch (e: java.text.ParseException) {
println("$d -> ParseException: ${e.message}")
}
}
}Output yang Dihasilkan:
=== Tangkap IOException ===
Tangkap IOException: FileNotFoundException
pesan: /tmp/berkas_tidak_ada.txt (No such file or directory)
=== Tangkap ParseException ===
input: 2026-01-15 -> sukses: Thu Jan 15 00:00:00 UTC 2026
input: bukan-tanggal -> ParseException: Unparseable date: "bukan-tanggal"
input: 2026/01/15 -> ParseException: Unparseable date: "2026/01/15"
=== Tangkap ClassCastException ===
Tangkap: ClassCastException
pesan: class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer are in module java.base of loader 'bootstrap')
=== Multi-catch ===
2026-01-15 -> sukses parse: Thu Jan 15 00:00:00 UTC 2026
tidak-valid -> format tidak dikenali
/tmp/file_lagi_tidak_ada -> IOException: /tmp/file_lagi_tidak_ada (No such file or directory)Mari kita telaah baris-baris kode di atas:
- Baris 14 (
fun bacaBarisPertama(path: String): String): fungsi Kotlin memanggilFileInputStream(path)yang di Java wajib dideklarasikanthrows IOException. Kotlin tidak memaksa deklarasi tersebut, sehingga signature Kotlin menjadi bersih. Bila path tidak ada,FileNotFoundException(subclassIOException) tetap dilempar saat runtime. - Baris 22 (
fun parseTanggalSimple(tanggalStr: String): Date):SimpleDateFormat.parse(String)di Java dideklarasikanthrows ParseException. Kotlin mengabaikan aturan ini, sehingga signature Kotlin hanya menampilkan tipe kembalianDatetanpa klausathrows. Tanggung jawab penanganan dialihkan ke pemanggil. - Baris 30 (
@Throws(IOException::class) fun bacaBarisPertamaUntukJava(...)): anotasi@Throwsmenambah klausathrowspada bytecode Java. Sisi Java yang memanggil method ini akan melihatString bacaBarisPertamaUntukJava(String) throws IOExceptiondan kompilator Java memaksa penanganan. Tanpa@Throws, sisi Java tidak tahu bahwa method dapat melempar checked exception. - Baris 39-44 (tangkap IOException): walaupun tidak wajib, try-catch tetap berfungsi.
e.javaClass.simpleNamemenghasilkanFileNotFoundException— subclass dariIOExceptionyang ditangkap dicatch. Pesan error berasal dari sistem operasi (linux). - Baris 47-56 (tangkap ParseException):
SimpleDateFormat.parsemelemparParseExceptionsaat format tidak cocok."2026-01-15"cocok pattern"yyyy-MM-dd"dan berhasil."bukan-tanggal"dan"2026/01/15"gagal parse dengan pesan jelas. - Baris 59-65 (tangkap ClassCastException): cast
apa as Intgagal karenaapaadalahString.ClassCastExceptionadalahRuntimeException(unchecked) di kedua bahasa, sehingga tangkapannya tidak ada hubungan dengan aturan checked. - Baris 68-87 (multi-catch): dua
catchterpisah menanganiIOExceptiondanParseExceptiondengan pesan berbeda. Kotlin juga mendukung sintakscatch (e: IOException | ParseException)(multi-catch single block), namun pemisahan dalam dua blok memberi pesan error yang lebih spesifik.
Kapan Memakai @Throws #
Anotasi @Throws hanya diperlukan bila kode Java akan memanggil method Kotlin dan kita ingin kompilator Java memaksa penanganan. Skenario tipikal:
- Library Kotlin yang dikonsumsi aplikasi Java (Spring Boot backend dengan kode controller Kotlin).
- Method Kotlin yang membungkus operasi I/O atau parsing berisiko.
- Method callback yang dipanggil framework Java (mis.
Callable,Comparator).
class LayananRepo {
@Throws(IOException::class)
fun baca(path: String): String { ... }
@Throws(IOException::class, JsonSyntaxException::class)
fun bacaJson(path: String): Map<String, Any> { ... }
}Tanpa @Throws, kode Java yang memanggil method ini akan menerima exception tanpa peringatan kompilator — bug tersekat yang sulit dideteksi.
Referensi Lanjutan #
- Calling Java from Kotlin — dokumentasi Java interop Kotlin untuk penanganan checked exception
Selanjutnya #
Bab berikutnya membahas static interop — bagaimana companion object Kotlin memetakan ke static method Java, dan bagaimana @JvmStatic serta @JvmField menghasilkan static sejati yang dapat dipanggil langsung dari Java tanpa perantara Companion.