In Kotlin, types are non-null by default. A variable of type String cannot hold null. To allow null, append ? to create a nullable type such as String?. The compiler enforces null checks at compile time.
Non-null vs nullable
Nullable vs non-null typesfun main() {
var name: String = "Nikhil"
// name = null // Compile error: Null cannot be assigned to non-null String
var nickname: String? = "Nick"
nickname = null // OK — String? allows null
println(name)
println(nickname) // null
}
Type
Can be null?
Example
String
No
val title: String = "Hello"
String?
Yes
val middleName: String? = null
Int
No
val age: Int = 25
Int?
Yes
val score: Int? = null
Nullable function parameters and returns
Nullable in functionsfun findUser(id: Int): String? {
return if (id == 1) "Nikhil" else null
}
fun greet(name: String?) {
if (name != null) {
println("Hello, $name")
} else {
println("Hello, Guest")
}
}
fun main() {
greet(findUser(1)) // Hello, Nikhil
greet(findUser(99)) // Hello, Guest
}
Kotlin vs Java null handling
Aspect
Java
Kotlin
Default
Any reference can be null
Non-null by default
NPE timing
Runtime crash
Many cases caught at compile time
Syntax
@Nullable / @NonNull annotations
? in type system
Smart cast: After an explicit if (x != null) check, Kotlin automatically treats x as non-null inside that block — no manual cast needed.
2Safe Calls
The safe call operator?. calls a method or accesses a property only if the receiver is non-null. If the receiver is null, the entire expression evaluates to null without throwing an exception.
Safe call chainsdata class Address(val city: String?)
data class User(val name: String, val address: Address?)
fun main() {
val user: User? = User("Alex", Address(null))
val city = user?.address?.city?.uppercase()
println(city) // null — safe chain stops at null city
val user2: User? = null
println(user2?.name) // null
}
Safe calls with let
Combine ?. with let to run a block only when a value is non-null:
?.let { }fun main() {
val email: String? = "user@example.com"
email?.let { validEmail ->
println("Sending to $validEmail")
}
val missing: String? = null
missing?.let {
println("This never runs")
}
}
Operator reference
Operator
Name
Behavior
?.
Safe call
Call/access only if non-null; else null
?[index]
Safe index
Safe array/list access on nullable receiver
as?
Safe cast
Returns null if cast fails (no ClassCastException)
Android example
binding?.textView?.text = userName — safely update UI when binding or view might be null during lifecycle transitions.
3Elvis Operator
The Elvis operator?: provides a default value when the left side is null. It is one of Kotlin's most used operators for concise null handling.
Elvis operator ?:fun main() {
val nickname: String? = null
val displayName = nickname ?: "Guest"
println(displayName) // Guest
val score: Int? = 85
val finalScore = score ?: 0
println(finalScore) // 85
}
Combining safe call and Elvis
?. combined with ?:fun main() {
val input: String? = null
val length = input?.length ?: 0
println("Length: $length") // Length: 0
val name: String? = "Kotlin"
println(name?.length ?: 0) // 6
}
Elvis with early return
Elvis for guard clausesfun processUser(name: String?) {
val validName = name ?: return
println("Processing $validName")
}
fun getLength(text: String?): Int {
return text?.length ?: throw IllegalArgumentException("text cannot be null")
}
fun main() {
processUser("Nikhil") // Processing Nikhil
processUser(null) // returns silently
}
Common patterns
Pattern
Code
Result if null
Default string
name ?: "Unknown"
"Unknown"
Default number
count ?: 0
0
Empty collection
list ?: emptyList()
Empty list
Parse or default
input.toIntOrNull() ?: 0
0
Name origin:?: looks like Elvis Presley's hair from the side — hence "Elvis operator."
4Non-Null Assertion
The non-null assertion operator!! converts a nullable type to a non-null type. If the value is actually null at runtime, Kotlin throws a NullPointerException.
Non-null assertion !!fun main() {
val name: String? = "Nikhil"
println(name!!.length) // 6 — OK
val missing: String? = null
// println(missing!!.length) // NPE at runtime!
}
When !! might be acceptable
Scenario
Reason
Immediately after null check elsewhere
You are certain value is non-null
Test code / prototypes
Quick and dirty; not for production
Interop with Java API
Legacy code guaranteed non-null (use sparingly)
Better alternatives to !!
Prefer safe alternativesfun main() {
val value: String? = getValue()
// Avoid:
// val len = value!!.length
// Prefer:
val len = value?.length ?: 0
// Or smart cast after check:
if (value != null) {
println(value.length) // smart cast — no !! needed
}
}
fun getValue(): String? = "Hello"
Warning
Treat !! as a code smell in production. It disables Kotlin's compile-time null safety and brings back Java-style NPE crashes. Prefer ?., ?:, smart casts, or explicit checks.
5Null Safety Best Practices
Follow these habits to write safer, cleaner Kotlin code in apps, libraries, and backend services.
Prefer non-null types
Default to String, Int, etc. Use ? only when null is a valid business state.
Use Elvis for defaults
value ?: default is readable and avoids nested if-null checks.
Chain safe calls
user?.profile?.avatar?.url — short-circuits safely on any null link.
Avoid !!
Replace with smart casts, requireNotNull, or early returns.
1. Use lateinit and by lazy appropriately
lateinit vs lazyclass MainActivity {
// lateinit — non-null var initialized later (not for primitives)
lateinit var adapter: RecyclerView.Adapter<*>
// lazy — computed once on first access
val config: Config by lazy { loadConfig() }
}
2. Use requireNotNull and checkNotNull
Explicit null checks with messagesfun saveUser(name: String?) {
val validName = requireNotNull(name) { "Name is required" }
println("Saving $validName")
}
fun parseId(id: String?): Int {
return checkNotNull(id?.toIntOrNull()) { "Invalid id: $id" }
}
3. Model optional data explicitly
Nullable fields in data classesdata class User(
val id: Int,
val name: String,
val email: String?, // optional
val phone: String? = null // optional with default
)
fun main() {
val user = User(1, "Nikhil", null)
println(user.email ?: "No email on file")
}
4. Platform types from Java
When calling Java code, Kotlin sees platform types (e.g. String!) — the compiler doesn't know if they're nullable. Treat Java return values as nullable unless documented otherwise.
Java interop caution// Java method: public String getName() { return maybeNull(); }
// Kotlin sees platform type String!
fun handleJavaName(javaUser: JavaUser) {
val name = javaUser.name ?: "Unknown" // safe default
}
5. Decision guide — which operator to use?
Situation
Recommended approach
Need default if null
value ?: default
Call method only if non-null
value?.method()
Run block only if non-null
value?.let { }
Already checked null in if
Smart cast — use value directly
Must fail if null
requireNotNull(value)
"I know it's not null"
Prove it with logic — avoid !!
Sealed classes for state: Instead of nullable UI state, use sealed classes like Loading, Success(data), Error(msg) — covered in Data, Enum & Sealed Classes.