Null safety is Kotlin's most celebrated feature, designed to eliminate NullPointerException at compile time. By default, types are non-nullable — declaring var name: String means the compiler will reject any attempt to assign null. To permit it, you append a question mark to declare a nullable type, as in String?. Once a type is marked nullable, the compiler tracks every place it is used and forces you to handle the null case explicitly, transforming what was a runtime hazard in languages like Java into a compile-time verification.
Three operators form the everyday vocabulary of null handling. The safe call operator ?. accesses a property or calls a method on a nullable receiver, but if the receiver is null, the entire expression short-circuits to null instead of throwing. For example, user?.address?.city returns null if user or address is null, and you can chain as many ?.'s together as needed — each short-circuits independently. To provide a default value when the left side is null, the Elvis operator ?: supplies an alternative: name?.length ?: 0 falls back to 0 whenever name is null. The right-hand side of ?: is evaluated only when needed, which makes the operator ideal for lazy fallbacks, and because it is right-associative with low precedence, idioms like a ?: b ?: c cleanly find the first non-null value. For situations where you are certain a value is non-null, !! casts it to non-null but throws a KotlinNullPointerException if that certainty proves wrong — used sparingly because it is a runtime escape hatch whose stack trace points to the !! site.
You can also check for null directly with == or != against null, and the compiler will smart cast the variable inside the conditional block, so it can be used as non-null without any operator at all. Combined with let, this yields an idiomatic null check: name?.let { println(it) } executes the block only when name is non-null, with the lambda's last expression as the result; if name is null, the whole let call returns null. Because let returns the lambda result while also returns the original object, the two are distinct tools for different needs — choose also when you want the receiver to flow through a chain unchanged, and let when you want a computed transformation.
When Kotlin calls Java, it encounters nullable types whose nullability is not annotated. These become platform types (sometimes shown as String! in tooling) and are treated as both nullable and non-nullable by the compiler — effectively shifting null-safety responsibility to the caller. To restore compile-time guarantees, you can annotate Java declarations with @NotNull and @Nullable (from JetBrains, JSR-305, or AndroidX), after which Kotlin enforces them like any native nullable type. Finally, throwing an exception produces a Nothing, which by being a subtype of every type lets you write val name = input ?: throw IllegalArgumentException("name required") without further typing noise.