Null Safety and Types

How does Kotlin null safety work?

Example

Types are non-null by default. String? may hold null, and the compiler forces you to handle it before use with ?. (safe call), ?: (Elvis default), !! (assert non-null) or a null check / let.

data class Address(val city: String?)
data class User(val name: String, val address: Address?)

fun main() {
    val users = listOf(
        User("Asha", Address("Pune")),
        User("Ravi", Address(null)),
        User("Meera", null),
    )
    for (u in users) {
        val city = u.address?.city ?: "Unknown"
        println("${u.name}: $city")
    }
    println("Meera city length = ${users[2].address?.city?.length}")
    users[0].address?.city?.let { println("Ship to $it") }
}

Output

Asha: Pune
Ravi: Unknown
Meera: Unknown
Meera city length = null
Ship to Pune

When can a NullPointerException still happen in Kotlin?

  • Using !! on a null value.
  • Calling Java code that returns null into a platform type (String!) you treated as non-null.
  • Accessing a lateinit property before it is set (throws UninitializedPropertyAccessException).
  • Leaking this from a constructor so an open member reads a property before it is initialized.
  • Explicitly throw NullPointerException().

What is the difference between lateinit and by lazy?

lateinit varval by lazy
Mutable (var)Read-only (val)
Assigned later by you (DI, onCreate, test setup)Computed on first access by the lambda, then cached
Non-null, non-primitive types onlyAny type, including primitives and nullable
Check with ::prop.isInitializedThread-safe by default (SYNCHRONIZED)
Example: lateinit var adapter: UserAdapterExample: val db by lazy { Room.databaseBuilder(...).build() }

What is a platform type?

A type coming from Java code without nullability annotations, shown as String!. Kotlin does not know if it can be null, so it lets you use it as either String or String? and skips null checks. Best practice: assign it to an explicitly typed variable (val name: String? = javaUser.getName()) and annotate Java APIs with @Nullable/@NonNull.

What is a smart cast?

Example

After an is check or a null check, the compiler automatically treats the variable as the narrower type, so no explicit cast is needed. It works on vals and local variables the compiler can prove have not changed.

fun describe(x: Any?): String = when (x) {
    null -> "null value"
    is String -> "String of length ${x.length}"
    is Int -> "Int doubled = ${x * 2}"
    is List<*> -> "List with ${x.size} items"
    else -> "Unknown: $x"
}

fun main() {
    listOf("kotlin", 21, listOf(1, 2, 3), null, 3.5).forEach { println(describe(it)) }
}

Output

String of length 6
Int doubled = 42
List with 3 items
null value
Unknown: 3.5

What is the difference between as and as?

Example

as is an unsafe cast and throws ClassCastException on failure. as? is a safe cast that returns null instead, usually combined with ?:.

fun main() {
    val value: Any = "123"
    val n: Int? = value as? Int
    println("as? result: $n")
    val s = value as String
    println("as result: ${s.toInt() + 1}")
    val safe = (value as? Int) ?: value.toString().toInt()
    println("fallback: $safe")
}

Output

as? result: null
as result: 124
fallback: 123

How are primitive types handled in Kotlin?

Kotlin has no separate primitive types in the language: you write Int, Double, Boolean. The compiler uses JVM primitives (int) where possible and boxes them (Integer) when they are nullable (Int?) or used as generic type arguments (List<Int>). Use IntArray instead of Array<Int> to avoid boxing. There are no implicit widening conversions: val l: Long = intValue does not compile, use toLong().