M
Mr Sugiarto
Developer
05 Sep 2026 5 min read

Di bab pengantar kita sudah lihat gambaran besar seri ini: 29 bab, 5 fase, membangun tiga service Kotlin + Spring Boot dari nol. Sebelum menyentuh baris kode Spring Boot pertama, kita perlu kuasai dulu fundamental bahasa Kotlin-nya sendiri — dan topik paling pertama, yang juga jadi salah satu alasan utama Kotlin dipilih untuk backend, adalah null safety.

Kenapa Null Safety Dulu?

Kalau kamu pernah nulis backend di bahasa yang tidak punya null safety di level compiler (Java lama, PHP tanpa strict types, JavaScript), kamu pasti pernah kena NullPointerException atau error setara di production — biasanya di titik yang paling tidak terduga, misalnya field response API eksternal yang ternyata kosong, atau hasil query database yang ternyata null karena baris tidak ditemukan. Ini bukan bug yang jarang; riset dan pengalaman industri berulang kali menempatkan NPE sebagai salah satu kelas bug runtime paling umum di aplikasi backend.

Kotlin menyerang masalah ini langsung di level tipe. Setiap tipe di Kotlin punya dua varian: versi non-null (misalnya String) dan versi nullable (misalnya String?). Compiler tahu persis mana yang boleh berisi null dan mana yang tidak, lalu menolak kompilasi kalau kamu mencoba memperlakukan nilai yang berpotensi null seolah-olah pasti ada isinya. Bug yang di Java baru ketahuan saat runtime (kadang cuma ketahuan di production), di Kotlin ketahuan saat kamu masih menulis kode, sebelum sempat di-commit.

Semua konsep di bab ini kita praktikkan lewat file test asli NullSafetyBasicsTest.kt di dalam project order-service — bukan potongan kode terpisah, tapi test yang benar-benar jalan dan hijau di project yang akan kita kembangkan sepanjang seri ini.

val vs var

Kotlin membedakan deklarasi variable yang bisa diubah nilainya (var) dan yang tidak (val, singkatan dari value, mirip final di Java atau const di sebagian bahasa lain). Konvensi di kode backend modern: pakai val sebagai default, dan hanya beralih ke var kalau memang ada alasan jelas nilainya akan berubah. Ini mengurangi kelas bug lain — state yang berubah diam-diam di tempat yang tidak terduga. Kalau kamu perhatikan seluruh isi NullSafetyBasicsTest.kt, hampir semua deklarasi memakai val; tidak ada satupun yang butuh diubah setelah diisi.

Tipe Non-Null: Dijamin Compiler, Bukan Runtime Check

Fungsi pertama di file test ini menerima parameter String biasa — artinya nilainya dijamin tidak pernah null:

// Tipe non-null: tidak boleh diisi null, dijamin compiler.
private fun formatCustomerName(name: String): String = name.trim().ifBlank { "Tanpa Nama" }

Perhatikan komentar di baris test-nya:

@Test
fun `non-null parameter tidak pernah menerima null - dijamin compiler, bukan runtime check`() {
    assertEquals("Budi", formatCustomerName("  Budi  "))
    assertEquals("Tanpa Nama", formatCustomerName("   "))
    // formatCustomerName(null) tidak akan pernah lolos compile - baris ini sengaja tidak ditulis.
}

Ini poin penting: kita tidak bisa menulis test yang memanggil formatCustomerName(null), karena project ini bahkan tidak akan berhasil di-compile kalau baris itu ada. Bandingkan dengan bahasa tanpa null safety — di sana, mencegah null masuk ke fungsi ini adalah tanggung jawab manual programmer (nulis if (name == null) throw ... di setiap fungsi, atau lupa dan berharap tidak kejadian di production). Di Kotlin, itu bukan tanggung jawab runtime sama sekali; itu pekerjaan compiler, sekali untuk selamanya.

ifBlank { } di sini juga contoh gaya idiomatis Kotlin: dia menerima lambda yang dieksekusi kalau string-nya kosong/whitespace setelah trim(), mengembalikan default "Tanpa Nama".

Tipe Nullable dan Safe Call (?.)

Kalau sebuah nilai memang secara alami boleh tidak ada — misalnya kode promo yang sifatnya opsional — kita tandai eksplisit dengan tanda tanya di belakang tipe:

// Tipe nullable (`String?`): boleh null, harus ditangani eksplisit di titik pemakaian.
private fun extractPromoCode(rawInput: String?): String? = rawInput?.trim()?.uppercase()

Di sini rawInput bertipe String?, jadi compiler tahu nilainya bisa null. Kalau kita coba tulis rawInput.trim() langsung tanpa ?., kompilasi akan gagal — compiler memaksa kita menghadapi kemungkinan null itu di titik ini juga, bukan menunda dan berharap tidak lupa. Operator ?. (safe call) artinya: "kalau nilainya null, langsung kembalikan null, jangan panggil method apapun." Rangkaian rawInput?.trim()?.uppercase() aman meskipun rawInput itu sendiri null — hasilnya null, bukan exception:

@Test
fun `safe call mengembalikan null tanpa melempar NullPointerException`() {
    assertNull(extractPromoCode(null))
    assertEquals("HEMAT10", extractPromoCode("  hemat10  "))
}

Elvis Operator (?:): Nilai Default Saat Null

Kadang kita tidak mau propagasi null terus — kita ingin memberi nilai default begitu ketemu null. Di situlah elvis operator (?:) dipakai. Fungsi promoDiscountPercent menggabungkan safe call dan when untuk memetakan kode promo ke persentase diskon, dengan 0 sebagai default kalau kode-nya null atau tidak dikenal:

// Safe call (?.) + elvis operator (?:) untuk memberi nilai default saat null.
private fun promoDiscountPercent(promoCode: String?): Int = when (promoCode?.uppercase()) {
    "HEMAT10" -> 10
    "HEMAT20" -> 20
    else -> 0
}

Perhatikan promoCode?.uppercase() — hasilnya tetap bertipe String? (bisa null kalau promoCode awalnya null), dan cabang else di when inilah yang berfungsi sebagai fallback, menangkap baik kasus null maupun kode yang tidak dikenal:

@Test
fun `elvis operator memberi nilai default saat input null`() {
    assertEquals(0, promoDiscountPercent(null))
    assertEquals(10, promoDiscountPercent("hemat10"))
    assertEquals(0, promoDiscountPercent("KODE_TIDAK_DIKENAL"))
}

Pola ?: yang lebih literal biasanya ditulis begitu: nilaiYangMungkinNull ?: nilaiDefault — "pakai nilai ini, atau kalau null, pakai ini." Sangat umum dipakai untuk validasi input request di controller nanti, misalnya request.quantity ?: 1 untuk default quantity kalau tidak dikirim client.

Kenapa Ini Penting untuk Kode Backend

Bayangkan Order Service yang akan kita bangun mulai Fase 1: setiap request JSON dari client, setiap baris hasil query database, setiap response dari service lain lewat HTTP — semuanya berpotensi membawa field yang kosong atau tidak ada. Di bahasa tanpa null safety di level tipe, developer harus mengingat sendiri field mana yang perlu dicek null — dan sekali lupa, celah itu biasanya baru ketahuan saat ada user nyata mengirim request yang "aneh" di production. Di Kotlin, penanda ? di tipe membuat kemungkinan ketiadaan nilai itu terlihat langsung di signature fungsi, dan compiler yang menjaga supaya tidak ada jalur kode yang melupakannya.

Modal null safety ini akan langsung relevan begitu kita masuk ke OOP di Kotlin pada bab berikutnya — terutama saat kita membahas data class dan sealed class untuk memodelkan domain Order Service secara lebih presisi.

M
Mr Sugiarto

Developer

Bagian dari Series: Kotlin Backend Microservice - Belajar Fundamental sampai Microservice Nyata

Belajar Kotlin + Spring Boot dari fundamental bahasa (null safety, OOP, coroutines) sampai membangun sistem microservice sungguhan - REST API, Postgre...

Lihat Series Lengkap