Search code examples
kotlinkotlin-null-safety

Does an ? operator on a non-null property prevent any NullPointerException in the chain?


I'm having trouble understanding the following change to a piece of Kotlin code:

package org.example.kotlin.nulll.resolution

import java.util.*

object UtilResources {

    private var properties: Properties? = null

    fun loadProperties() {
        properties = Properties()
        properties?.load(UtilResources.javaClass.getResourceAsStream("/config.properties"))
    }

    fun getProperties(properties: String): String {
        loadProperties()
        System.out.println(UtilResources.properties)
        System.out.println(UtilResources.properties?.getProperty(properties))
        return UtilResources.properties?.getProperty(properties).toString()
    }
}

works fine, i.e. returns the property value loaded from config.properties or "null" in case the property isn't present in the file.

If I change the code to be null-safe to

object UtilResources {

    private val properties: Properties = Properties()

    fun loadProperties() {
        properties.load(UtilResources.javaClass.getResourceAsStream("/config.properties"))
    }

    fun getProperties(properties: String): String {
        loadProperties()
        System.out.println(UtilResources.properties)
        System.out.println(UtilResources.properties.getProperty(properties))
        return UtilResources.properties.getProperty(properties).toString()
    }
}

I'm getting NullPointerException because UtilResources.properties.getProperty(properties) is null which I'm verifying with the System.out.println Statements.

In the first version properties is not null just like in the second, so the ? operator is the only thing that's changing afaik. However, it's placed after a non-null property, so it shouldn't have any effect.

null.toString() should always work because it's an overloading extension function in Kotlin.

Assume that config.properties exists and that UtilResources.javaClass.getResourceAsStream("/config.properties" returns a non-null value. You can find an SSCCE project at https://gitlab.com/krichter-sscce/kotlin-null-resolution. It doesn't contain more information than this question.

Please note that I'm not seeking debugging support, I'd like to understand what's going on and broaden my understanding of Kotlin. I didn't manage to condense the example further.

I'm using Kotlin 1.4.31 through Maven 3.6.


Solution

  • In your first version Kotlin knows that you are calling toString() on a nullable type, since you are calling getProperty()on the Nullable field properties, wherefore Kotlins extension function Any?.toString() ist used.

    In your second version Kotlin doesn't knows that you are calling toString()on a Nullable type, since you are calling getProperty()on a field that cannot be null and the function getProperty() is a Java function that doesn't defines a Nullable return value. Therefore the extension function isn't used and a NPE is thrown if the property doesn't exists.

    The following code works as expected:

    object UtilResources {
    
        private val properties: Properties = Properties()
    
        fun loadProperties() {
            properties.load(UtilResources.javaClass.getResourceAsStream("/config.properties"))
        }
    
        fun getProperties(key: String): String {
            loadProperties()
            println(properties)
            println(properties.getProperty(key))
            return properties.getOptionalProperty(key).toString()
        }
    
        private fun Properties.getOptionalProperty(key: String): String? = getProperty(key)
    }