Kotlin – Using ?. and ?.let {} to Avoid Null Checks

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)?

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 name is not null, it prints the length.
  • If name is null, it simply returns null without 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 name is not null.
  • If name is 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)

Java

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 {}

Kotlin

val user: User? = getUserFromApi()
user?.let {
    println("User ID: ${it.id}")
    println("User Name: ${it.name}")
} ?: println("User not found!")
  • If user is not null, the block executes.
  • If user is 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 email is not null, sendEmail(it) executes.
  • If email is null, it prints "Email is missing!".

6. Complete Kotlin Example: Using ?. and ?.let Together

KotlinTest.kt

// 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 ?.let with ?: 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.

Reference

mkyong

Founder of Mkyong.com, passionate Java and open-source technologies. If you enjoy my tutorials, consider making a donation to these charities.

0 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments