How To Use Nullable Types Correctly in Kotlin

One of the most annoying aspects in writing code in Java is dealing with null elements. Having to place null check conditions everywhere just to make our code null-safe is something that probably most of us have suffered quite often.

Kotlin provides different mechanisms to help solve this problem. Let’s go through them first.

Non-Nullable Types

By default Kotlin types are not nullable, what means that it’s impossible to assign null to them. That’s definitely the best way to avoid having to deal with nulls in our language; although is this always possible?

Not always, we might have a findEmployee method in our service where we want to express the possibility of not finding the employee by using a Nullable type.

If we take the example we used in our last Kotlin article about Either:

fun findEmployeeByName(name: String): Either<Problem, Employee> {
val request = Request(Method.GET, "http://myservice.org/employees").query("name", name)
val response = client(request)
return when (response.status) {
Status.OK -> Employee(response.bodyString()).right()
else -> HttpFailureProblem("Http call failed with status ${response.status} due to ${response.bodyString()}").left()
}
}

We can slightly modify it to include the possibility of not finding the employee; to achieve that we’ll use a nullable type. We could also use a Problem for that, but that would depend if you want to represent that as a valid business case or as an exceptional case. In our case we’d go for the option of using a nullable type. So how would it look like?

fun findEmployeeByName(name: String): Either<Problem, Employee?> {
val request = Request(Method.GET, "http://myservice.org/employees").query("name", name)
val response = client(request)
return when (response.status) {
Status.OK -> Employee(response.bodyString()).right()
Status.NOT_FOUND -> Either.Right<Employee?>(null)
else -> HttpFailureProblem("Http call failed due to ${response.bodyString()}").left()
}
}

As you can see, we have mapped a 404 (NOT_FOUND) response to a null Employee for our nullable Employee response.

How would this be handled from the client side? Let’s take a quick look.

Handling Nullable Types

One of the things that Kotlin beginners tend to do is to use the !! operator every time a compilation issue has to be solved between a nullable and non-nullable type. This is a big mistake and it should be avoided; !! will throw a NullPointerException if the instance is null. As a rule of a thumb, always avoid using !! operator unless that it’s really necessary.

If we take our PayrollService as an example, we could for example have this:

fun generatePayrollFor(employeeName: String): Either<Problem, Payroll> {
return employeeService.findEmployeeByName(employeeName)
.flatMap { employee ->
buildPayrollForEmployee(employee!!)
}.tapLeft {
logger.info("Could not fetch employee: ${it.message()}")
}
}

That’s simple but unfortunately it will raise a NullPointerException when our employee is null; if you’re thinking about catching it and then do something else with it, please don’t!

So what can we do to avoid using !! operator then? Let’s see what other options we could have.

Null checks

One of the options is of course to check if the instance is null with an if condition. If checks are a bit verbose though and they can pollute our code considerably. We can take our PayrollService as an example to see how all these options would look like; once we introduce null checks it would be something like this.

fun generatePayrollFor(employeeName: String): Either<Problem, Payroll> {
return employeeService.findEmployeeByName(employeeName)
.flatMap { employee ->
if (employee != null) {
buildPayrollForEmployee(employee)
} else {
UserDoesNotExistProblem(employeeName).left()
}
}.tapLeft {
logger.info("Could not fetch employee: ${it.message()}")
}
}

Although this option is not a bad way of handling it, one of the problems in using if conditions is that it takes multiple lines just to do a very simple check, something that will end up polluting our code in multiple places. In our example it might be not so obvious but what if we just want to log a field? We’d have to embed a whole if condition in our logger statement or do some checks before calling it. So ideally we’ll need something much more concise to be able to use it in a one-liner.

Another problem of null checks is that it’s not an atomic operation, so in the cases when we’re dealing with mutable instances we could fall into big problems due to the value changing right after the null check.

We could also use a more concise version of our if condition, although this is not ideal either and won’t solve some of the issues null checks brings.

fun generatePayrollFor(employeeName: String): Either<Problem, Payroll> {
return employeeService.findEmployeeByName(employeeName)
.flatMap { employee ->
if (employee != null) buildPayrollForEmployee(employee) else UserDoesNotExistProblem(employeeName).left()
}.tapLeft {
logger.info("Could not fetch employee: ${it.message()}")
}
}

Let’s see what else could we do in Kotlin.

Safe calls

A good tool Kotlin provides are safe calls. We can make use of safe calls by using the operation ?. ; this will safely access a null instance or its properties. We could also apply an operation over that instance by executing any action over it using let method.

It’s worth mentioning that a safe call will return null if the instance was null. For instance employee?.name will be “safely” returning null if the employee instance was null, instead of raising a NullPointerException.

For example, let’s say we want to log the employee name in one of our log statements. We could easily do that as shown below; in the case of our employee being null, null will be printed in our log.

logger.info("Processing employee '${employee?.name}'")

This leads to the next question; what if we want to provide a default value for the employee name in our log?

Elvis operator

The elvis operator allows us to define a non-nullable value to be used in the case that the left hand-side returns null.

For example, in we wanted to define a default value in our log statement we could have something as:

logger.info("Processing employee '${employee?.name ?: "Employee not found."}'")

As simple as that!

Now knowing all we’ve learned, what would be the best way to handle a nullable type from our PayrollService?

Putting all together

With all the options we have now a clean, concise, safe and expressive way to handle a nullable Employee type could be something like this:

fun generatePayrollFor(employeeName: String): Either<Problem, Payroll> {
return employeeService.findEmployeeByName(employeeName)
.flatMap { it?.let { buildPayrollForEmployee(it) } ?: UserDoesNotExistProblem(employeeName).left() }
.tapLeft {
logger.info("Could not fetch employee: ${it.message()}")
}
}

That looks concise and clean; there could be some arguments against it due to not being “human-readable” but I can promise that once you get used to the use of safe calls and let methods you’ll read it clearly with no effort.

It’s also worth mentioning that if you are working with mutable variables (something I would NOT recommend), using let is safer than using an if condition. This is because the value passed to the lambda inside the let method will not change even if the original variables changes its value; that will make your block much safer in this particular case.

Conclusion

Kotlin has made big improvements into one of the areas where Java has been highly criticised due to all the problems caused around it. People like Tony Hoare have even apologised for inventing null in the first place, so we’re not talking about a trivial problem; this has been costly and painful in many languages for a variety of reasons.

That’s all for now! We hope you’ve really enjoyed and learned something useful this time! One last thing, if you feel you need to improve your Kotlin knowledge and need a good book, we recommend taking a look at Kotlin in action or Head First Kotlin. We consider these good resources to start learning Kotlin and have a basic understanding to start writing applications with it.

Thanks for reading us and please subscribe if you like our content!

Up ↑

Take a look at our recommended books!

Ok!
X
%d bloggers like this: