1. Overview

Null-safety is a nice feature in Kotlin. It allows us to handle nullable values comprehensively.

In this tutorial, let’s look into the compilation error “Smart cast to <type> is impossible.”, caused by Kotlin’s smart-casting a nullable type to the corresponding not-nullable type.

2. Introduction to the Problem

First of all, let’s look at a simple class:

class NiceOne {
    val myString: String? = null
    val notNullList: MutableList<String> = mutableListOf()

    fun addToList() {
        if (myString != null) {
            notNullList += myString
        }
    }
}

As the code above shows, the NiceOne class has an immutable and nullable String attribute (myString). Also, it has a mutable list attribute notNullList that only accepts not-null strings.

In the addToList() function, we first check the attribute myString. If it’s not null, we add it to notNullList. Here, we’ve used the built-in MutableCollection‘s plusAssign() operator overloading (+=) to add the element.

The class compiles without any problem. This is because after the null check when we try to add a nullable string to a list that only accepts not-null strings, Kotlin smart-casts nullable type String? to the not-nullable type String.

Now, let’s say we’ve got a new requirement, and we need to make the myString variable mutable. So, we can just change the val keyword into var:

class NiceOne() {
    var myString: String? = null
}

But, after this tiny change, if we try to compile the class, we’ll see the compilation error message:

Kotlin: Smart cast to 'String' is impossible, because 'myString' is a mutable property that could have been changed by this time

W get this error on the line where we add the myString variable to the list:

if (myString != null) {
   notNullList += myString
               // ~~ ^^ ~~ compilation error

So, next, let’s take a closer look at this error and understand why Kotlin complains and how to fix it.

3. Why Does Kotlin’s Smart Casting Work on val but Fail on var?

We know that variables declared by the val keyword are immutable in Kotlin. Therefore, if we’ve checked that an immutable variable is not null, although it’s a nullable type, it’s safe to cast it to its corresponding not-nullable type. That’s why Kotlin’s smart-casting works on immutable variables.

On the other hand, if a nullable variable is mutable, even though we’ve checked that its value is not null, we still cannot guarantee its value won’t be changed to null right before we cast it to the non-nullable type.

Let’s review our example. In our class, var myString: String? is a class attribute. So we can access it through an instance of the NiceOne class. Let’s say we have an instance niceOne.

Even if we’ve checked, niceOne.myString is not null before Kotlin casts String? to String, another thread may have assigned the null value to niceOne.myString:

fun addToList(){
    if (myString != null) {
       // at this point, another thread may have changed myString's value to null
       notNullList += myString

Kotlin doesn’t know when the value of this variable is modified and whether it has been set to null. Therefore, Kotlin rejects smart-casting.

Now that we understand the error’s cause, let’s explore how to fix it.

4. Prefer Not-Nullable Types

When we’re facing the mentioned problem, first, we can reconsider our design and see whether it’s possible to declare the variable as the not-nullable type. But, of course, this is entirely dependent on the design and the requirement.

For example, we’re sure that the variable will always be set before we first time use it at runtime. However, we cannot predict its initial value. In this case, we can consider using Kotlin’s lazy initialization(lateinit var … ):

class NiceTwo {
    val notNullList: MutableList<String> = mutableListOf()
    private lateinit var myString: String

    private fun determineString() {
        myString = if (notNullList.size % 2 == 0) "Even" else "Odd"
    }

    fun addToList() {
        determineString()
        notNullList += myString
    }
}

As the example above shows, we’ve defined myString with the not-nullable String. Further, the determineString() function is responsible for initializing the mutable myString variable.

Let’s create a test to verify whether it works:

val two = NiceTwo()
two.addToList()
assertThat(two.notNullList).containsExactly("Even")
two.addToList()
assertThat(two.notNullList).containsExactly("Even", "Odd")

Of course, if we access the lateinit variable before its initialization, we’ll get an UninitializedPropertyAccessException. In this case, we likely find a bug of forgetting to initialize the lateinit variable.

We’ve seen using a not-nullable type can save us from the casting problem. However, we sometimes do want the variable to carry a null value. So next, let’s see how to deal with this case.

5. Making a Local Copy

We’ve understood the cause of this error is that another thread can modify a mutable class property. Also, we know that all function local variables are thread-safe as they are created in stacks.

That is to say, if we copy the class property’s value to a local variable, the local copy won’t be changed by other threads. Therefore, Kotlin’s smart-casting should work on the local copy.

5.1. Copying to a Local Variable

Now, let’s modify the addToList() function to work with a local copy of myString:

class NiceThree {
    var myString: String? = null
    val notNullList: MutableList<String> = mutableListOf()

    fun addToList() {
        val myCopy: String? = myString
        if (myCopy != null) {
            notNullList += myCopy
        }
    }
}

Next, let’s create a test and check if this approach works as expected:

val three = NiceThree()
three.myString = "One"
three.addToList()
assertThat(three.notNullList).containsExactly("One")

three.myString = "Two"
three.addToList()
assertThat(three.notNullList).containsExactly("One", "Two")

three.myString = null
three.addToList()
assertThat(three.notNullList).containsExactly("One", "Two")

If we run the test, it passes. So, it means smart-casting works with the local copy.

5.2. Using the Safe Call Operator (.?) and a Scope Function

Just now, we’ve learned the two-step solution to the smart-casting problem:

  • Making a local copy
  • Checking the value is not null

In Kotlin, we can use the safe call operator (.?) and a scope function to apply these two steps idiomatically:

class NiceFour {
    var myString: String? = null
    val notNullList: MutableList<String> = mutableListOf()

    fun addToList() {
        myString?.let { notNullList += it }
    }
}

As the code above shows, we safely call the let() function only if myString isn’t null. Also, we should note that the variable it is a local copy of the not-nullable myString value within the let() function.

Next, as usual, let’s verify if this solution works by a test:

val four = NiceFour()
four.myString = "One"
four.addToList()
assertThat(four.notNullList).containsExactly("One")

four.myString = "Two"
four.addToList()
assertThat(four.notNullList).containsExactly("One", "Two")

four.myString = null
four.addToList()
assertThat(four.notNullList).containsExactly("One", "Two")

Again, the test passes if we give it a run.

As we’ve seen, making a local copy of the nullable variable solves the smart-casting problem. However, we should note when we make a local copy, we use the variable’s snapshot copy instead of its real-time value.

6. Using the Not-Null Assertion Operator (!!)

Sometimes, it depends on the requirement, and we may need to use a variable’s real-time value instead of a snapshot copy. Therefore, we cannot take the approach of making a local copy.

If this is the case, we can use the not-null assertion operator (!!):

class NiceFive {
    var myString: String? = null
    val notNullList: MutableList<String> = mutableListOf()

    fun addToList() {
        try {
            if (myString != null) {
                notNullList += myString!!
            }
        } catch (ex: java.lang.NullPointerException) {
            // exception handling omitted
        }
    }
}

As we can see in the function NiceFive.addToList(), we use myString!!. This will use the variable myString‘s real-time value for the following operation, which adds it to notNullList.

Further, we’ve added a try-catch block as !! may throw NullPointerException if the variable value is null. In the example, we’ve omitted the exception handling. However, in the real world, we need to deal with the NullPointerException exception properly.

Finally, let’s create a test to check if the code above compiles and works as expected. For simplicity, we skip the NullPointerException case:

val five = NiceFive()
five.myString = "One"
five.addToList()
assertThat(five.notNullList).containsExactly("One")

five.myString = "Two"
five.addToList()
assertThat(five.notNullList).containsExactly("One", "Two")

five.myString = null
five.addToList()
assertThat(five.notNullList).containsExactly("One", "Two")

If we run the test above, the code compiles without any error, and the test passes too.

7. Conclusion

In this article, we’ve discussed why the Kotlin compiler may raise an error when smart-casting a nullable type to the corresponding not-nullable type. Further, we’ve explored a few different approaches to solve the problem.

As always, the full source code used in the article can be found over on GitHub.

Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.