Rules About Kotlin Null Checks

Josh Feinberg
4 min readDec 10, 2019

A couple months back I got into a discussion over the use of the not-null assertion operator (!!) in Kotlin and the final discussion boiled down to this:

The rule on !! is there is don't have a rule. If you have a rule, get rid of it. Otherwise good job.

However, that would make a very poor blog post, so instead let’s chat about the options you have, some reasons you might choose them, and hopefully you’ll understand why rules against the not-null assertion operator are bad.

Option 1: Not-null Assertion Operator

The official Kotlin docs start off explaining this as:

The third option is for NPE-lovers

That isn’t exactly a glowing recommendation, however it still does have its uses.

Sometimes as a developer we are able to infer something that an IDE cannot. An example could be in a validation function:

Since myInput is a nullable type, even though we check in the isInputValid function and assert that myInput is not null, the compiler cannot confidently state that myInput is no longer null in the scope of that if statement and will not compile.

This is where someone would bring up contracts, but this feature is still experimental which some code bases will not allow. Also, the developer may not own the function so that would not provide a good solution.

Since, as a user, we can be confident that variable is never null it would make sense here to use the not-null assertion operation for myInput!!.length. It should be used carefully but as long as the developer could argue why this would never throw, it does not make sense to create a rule against using this.

Option 2: Elvis Operator with a throw

The elvis operator with a throw ends up looking something like this:

val nonNullValue = arguments?.getString("KEY") ?: throw IllegalStateException("Unable to find string KEY in arguments")

The benefit of this solution is it allows you to better define an error message which could help track things down better. Doing a not-null assertion would result in a KotlinNullPointerException with no information about what is null other then the line it occurred on. By switching to the throw, you can write out a better explanation of what happened and include any debugging information to send to your crash logger. You also get the added benefit that the compiler will smartcast to a non-nullable type so you can use it safely later in your code.

Option 3: requireNotNull

requireNotNull is a function is kotlin’s standard library that throws an IllegalArgumentException if the value is null otherwise returns the not-null value. Since it is written using contracts, you don’t even need to use the return value.

To use the example from before, you can simply add that line before the length variable declaration.

We know that it will never throw (or if it does, it’s an error we should know about) and because of the contract we are safe to use myInput as a non-null variable.

Option 4: The Safe Unwrap (?.)

The safe unwrap is probably the solution most developers would usually go to but could lead to some trouble.

textViewState?.let { state ->
textView.visibility = state.visibility
textView.text = state.text
}

This code would safely run whether or not our textViewState is null but a lot of developers could end up in a weird side effect.

The possible side effect would be that is unknown what are the default values of that textView. If we aren’t expecting textViewState to be null, then we could have some unexpected defaults and our view might show up inappropriately in the layout or have some demo text that we expected to be removed. We are now hiding a potential issue that we would want to know about (that textViewState is null) and instead showing the user an unexpected state.

You could argue that you just need an elvis check on the end but you are opening yourself up to side effects explained in my other post.

That is not to say everything about this solution is bad. It can be used well when combined with the throw shown in option one, or if you aren’t worried about what happens if it is null. A good scenario would be in Android development when setting an item on a view. textView?.text = "TEXT" could be a good use of the safe unwrap because you don’t have control over the view lifecycle so your view could have gotten nulled out at this point.

To sum it up, I think that all the solutions have their place. The original point still stands that having set rules is probably more trouble then it is worth because each scenario will lead to its own needs. A developer should evaluate what solution works well for their piece of code and be able to defend that situation.

So just remember, the best rule is probably no rule when it comes to handling null in Kotlin.

--

--