Kotlin's null safety system eliminates the infamous NullPointerException at compile time. It's one of the most loved features of the language.
Nullable Types
Kotlin
// Non-null (default)
var name: String = "Alice"
// name = null // COMPILE ERROR
// Nullable (add ? after type)
var maybeName: String? = "Alice"
maybeName = null // OK
// Nullable collections
val nonNullList: List<String> = listOf("a", "b")
val nullableList: List<String?> = listOf("a", null, "b")Safe Calls
Kotlin
// ?. — Safe call operator
val length: Int? = maybeName?.length // null if maybeName is null
// Chained safe calls
data class Address(val city: String?)
data class User(val address: Address?)
val user: User? = User(Address("New York"))
val city = user?.address?.city // "New York"
val userNull: User? = null
val cityNull = userNull?.address?.city // null (no crash!)Elvis Operator
Kotlin
// ?: — Elvis operator (default value if null)
val length = maybeName?.length ?: 0 // 0 if null
// With function calls
val name = getUserName() ?: "Anonymous"
// With multiple fallbacks
val value = primaryValue() ?: secondaryValue() ?: defaultValue()Let Operator
Kotlin
// ?.let { } — Execute block only if not null
maybeName?.let { name ->
println("Name is $name, length is ${name.length}")
}
// Common patterns
val email: String? = "test@email.com"
email?.let {
sendWelcomeEmail(it) // Only sends if email is not null
}
// Transform if not null
val upperName = maybeName?.let {
it.uppercase()
}Not-Null Assertion
Kotlin
// !! — Force unwrap (throws NPE if null — use sparingly!)
val length = maybeName!!.length // KIException if null
// Only use when you're CERTAIN it's not null
val user = getCurrentUser()!! // Acceptable if guaranteed by logicSmart Casts
Kotlin
fun process(value: Any) {
// Kotlin automatically casts after null check
if (value is String) {
println(value.length) // Smart cast to String
}
// Safe cast with as?
val num: Int? = value as? Int // null if not Int
when (value) {
is String -> println("String: $value")
is Int -> println("Int: $value")
is List<*> -> println("List with ${value.size} items")
else -> println("Unknown type")
}
}Require and Check
Kotlin
// require — validates function arguments
fun setAge(age: Int) {
require(age in 0..150) { "Age must be between 0 and 150" }
// Throws IllegalArgumentException if invalid
}
// check — validates internal state
fun processUser(user: User?) {
check(user != null) { "User must not be null" }
// Throws IllegalStateException if null
}
// assert — debug-only check (disabled in release)
assert(user.age > 0)Platform Types
Kotlin
// Java interop: Java types are "platform types" (String! or Int!)
// They can be either nullable or non-null
val javaString: String = javaMethod() // Assume non-null
val javaMaybeString: String? = javaMethod() // Assume nullable
// Always be explicit with Java return values
fun handleJavaResult(result: String?) {
result?.let {
processString(it)
} ?: handleNull()
}💡 Tip: Use
?.letfor one-off operations on nullable values, and the Elvis operator for default values. Avoid!!in production code — it hides bugs that the compiler could catch.
Next: Kotlin Classes