One of the most common issues in Java programming is dealing with null values, which can lead to the dreaded NullPointerException (NPE). Kotlin, however, provides built-in features to handle null safety in a more elegant way. Two of the most useful tools for this are ?. (safe call operator) and ?.let {} (scope function).
In this article, we’ll explore how to use these operators to write cleaner, safer, and more concise Kotlin code.
Table of contents
- 1. What is ?. (Safe Call Operator)?
- 2. What is ?.let {} (Scope Function)
- 3. ?. vs ?.let {} – When to Use What?
- 4. Real-World Example: Handling API Response
- 5. Combining ?.let {} with the Elvis Operator (?:)
- 6. Complete Kotlin Example: Using ?. and ?.let Together
- Conclusion
- Reference
1. What is ?. (Safe Call Operator)?
The safe call operator (?.) allows you to safely access a property or method of a nullable object without checking for null explicitly.
Example: Accessing a Property Safely
val name: String? = getUserName()
println(name?.length) // If name is null, prints null instead of throwing an exception
- If
nameis not null, it prints the length. - If
nameis null, it simply returnsnullwithout crashing.
Use ?. when:
- You only need to access a property/method safely.
- You don’t need additional logic.
2. What is ?.let {} (Scope Function)
The let function is a scope function that executes a block of code only if the object is not null. This is useful when you need to perform multiple operations on a nullable value without writing explicit null checks.
Example: Performing Multiple Operations Safely
val name: String? = getUserName()
name?.let {
println("Hello, $it")
println("Length: ${it.length}")
}
- The block executes only if
nameis not null. - If
nameis null, the block is skipped.
Use ?.let {} when:
- You need to execute multiple operations safely.
- You don’t want to repeat the variable name.
- You want to scope logic only when non-null.
3. ?. vs ?.let {} – When to Use What?
| Use Case | ?. (Safe Call) |
?.let {} (Scope Function) |
|---|---|---|
| Access a property/method | ✅ Simple and direct | 🚫 Unnecessary |
| Perform multiple operations | 🚫 Not possible | ✅ Cleaner and safer |
| Avoid repeating variable name | 🚫 Requires variable name | ✅ Uses it |
Chain with ?: (Elvis Operator) |
✅ Works well | ✅ Works well |
Example of Using Both Together
val user: User? = getUserFromApi()
// Using ?. for property access
println(user?.name)
// Using ?.let {} for multiple operations
user?.let {
println("User ID: ${it.id}")
println("User Name: ${it.name}")
}
4. Real-World Example: Handling API Response
Java Approach (Explicit Null Checks)
User user = getUserFromApi();
if (user != null) {
System.out.println("User ID: " + user.getId());
System.out.println("User Name: " + user.getName());
}
This approach requires explicit null checks and can clutter the code.
Kotlin Approach Using ?.let {}
val user: User? = getUserFromApi()
user?.let {
println("User ID: ${it.id}")
println("User Name: ${it.name}")
} ?: println("User not found!")
- If
useris not null, the block executes. - If
useris null, it prints "User not found!".
5. Combining ?.let {} with the Elvis Operator (?:)
You can combine ?.let {} with ?: to execute an alternative action when the value is null.
val email: String? = getUserEmail()
email?.let {
sendEmail(it)
} ?: println("Email is missing!")
- If
emailis not null,sendEmail(it)executes. - If
emailis null, it prints "Email is missing!".
6. Complete Kotlin Example: Using ?. and ?.let Together
// Define a User data class
data class User(val id: Int, val name: String?, val email: String?)
// Function that may return a User or null (simulating an API response)
fun getUserFromApi(userId: Int): User? {
return if (userId == 1) User(1, "Mkyong", "[email protected]") else null
}
fun main() {
val user: User? = getUserFromApi(1) // Try changing this to getUserFromApi(2) for a null case
// Using ?. (Safe Call) to access properties safely
println("User Name: ${user?.name}") // Prints name if not null, otherwise prints "null"
println("User Email Length: ${user?.email?.length}") // Calls length only if email is not null
// Using ?.let {} to perform multiple operations only if user is not null
user?.let {
println("User Details:")
println("ID: ${it.id}")
println("Name: ${it.name}")
println("Email: ${it.email ?: "No email provided"}")
} ?: println("User not found!") // This runs if user is null
// Using ?.let {} with a list (Processing only if not null)
val numbers: List<Int>? = listOf(1, 2, 3, 4, 5)
numbers?.let {
println("List Size: ${it.size}")
println("First Element: ${it.first()}")
}
// Using ?.let {} to prevent calling function on null objects
val nullList: List<Int>? = null
nullList?.let {
println("This won't print because nullList is null")
} ?: println("List is null, skipping processing.")
}
Expected Output
If getUserFromApi(1) returns a valid user:
User Name: Mkyong
User Email Length: 18
User Details:
ID: 1
Name: Mkyong
Email: [email protected]
List Size: 5
First Element: 1
List is null, skipping processing.
If getUserFromApi(2) returns null:
User Name: null
User Email Length: null
User not found!
List Size: 5
First Element: 1
List is null, skipping processing.
Conclusion
- Use **
?.(safe call operator) when you need simple property or method access. - Use **
?.let {}(scope function) when you need multiple operations on a non-null object. - Combine
?.letwith?:for clean and safe null handling.
By using these Kotlin features, you can write safer, cleaner, and more concise code compared to traditional Java null checks.