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.
No comments yet. Be the first to leave a comment!