1. Introduction

Kotlin provides us with an option to provide default values in class constructors. There are, however, situations where this feature does not help us achieve what we require. One example of this is when we want to use a default value on a non-optional parameter when passing a null value.

In this article, we’ll be exploring different approaches to solving this issue in a hassle-free way.

2. Non-Optional Null Parameters

First, let’s explore the problem described in the introduction. Let’s say we have the following entity:

data class Person(val name: String, val age: Int)

Usually, when we want to make some decisions about the default value, we can do so with Kotlin’s built-in default parameter syntax:

data class PersonWithDefaults(val name: String = "John", val age: Int = 30)

The problem that we want to answer in this article comes when we have some incoming values that can be null. Then, we need to remember to inline the proper defaults:

val nullableName: String? = "John"
val nullableAge: Int? = null

Person(
    name = nullableName ?: "John", // Ouch
    age = nullableAge ?: 0 // Ouch
)

This leads to the question we want to answer in this article: Is there a better way to have a default value for a non-optional parameter when null is passed?

3. Defaulting Null Values

Thanks to Kotlin’s many features, there are quite a few ways to address this issue. Let’s go through them one by one.

3.1. Class Properties

One option is to use the properties of the class itself:

class PersonClassSolution(nullableName: String?, nullableAge: Int?) {
    val name: String = nullableName ?: "John"
    val age: Int = nullableAge ?: 30
}

We’re leveraging the fact that what we pass via the constructor and what we expose from the class can be two different things. Alternatively, the same can be achieved with a data class:

data class PersonDataClassSolution(private val nullableName: String?, private val nullableAge: Int?) {
    val name: String = nullableName ?: "John"
    val age: Int = nullableAge ?: 30
}

By using private for constructor variables, we force users to use the non-null fields we defined. Let’s take a look at what other Kotlin features we can use to achieve the same result.

3.2. Secondary Constructor

Kotlin’s data class requires defining fields in the primary constructor. With this constraint in mind, if we want to have the non-null variables as primary ones, we need to solve this issue using another mechanism. One of the solutions is to use a secondary constructor:

data class PersonWithAdditionalConstructor(val name: String, val age: Int) {
    constructor(name: String?, age: Int?) : this(name ?: "John", age ?: 0)
}

This approach will work just fine, but it exposes the constructor without the defaults. If we don’t want to expose it, then we could try using Kotlin’s getter feature.

3.3. Getter

Kotlin allows customizing getters and setters to tweak their functionality. We can take advantage of this feature to get a result that is very similar to the one in the previous example:

data class PersonWithGetter(private val nullableName: String?, private val nullableAge: Int?) {
    val name: String
        get() = nullableName ?: ""

    val age: Int
        get() = nullableAge ?: 0
}

Thanks to getters, we again restrict access to nullable variables and expose non-nullable ones. The resulting code looks very similar to the “class way” described in section 3.1.

3.4. invoke Operator

A final solution that we can use is to make the main constructor private and use operator overloading to achieve the result we require. Before we go into the code, it might be useful to get a better understanding of how operator overloading works in Kotlin.

Let’s see an example that leverages the invoke operator:

data class PersonWithInvoke private constructor(val name: String, val age: Int) {
    companion object {
        operator fun invoke(name: String?, age: Int?) = PersonWithInvoke(name ?: "John", age ?: 0)
    }
}

Thanks to the invoke operator, we have a clean entity creation with defaults for null values, while preserving the quality of usage with non-null values.

4. Conclusion

In this article, we’ve taken a look at different ways we can default a null value for a non-optional parameter. We discovered that thanks to Kotlin’s many features, there are quite a few possible approaches.

As always, code from this article is available 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.