The Known Unknown: Working with Option and Nullable Types

Generated by imgflip

Speaking with some developers (mostly having a Java background) you will sooner or later hear a quotation like “Optional is a safe way for handling null”. Programmers on the JVM to a great extent mistakenly use the Option type (known as Optional in Java) to just handle null values as a sophisticated alternative to if(x == null) do this else do that.

Having to handle null in Java is so omnipresent that this conclusion is quite easy to draw. But this is just a Java-biased view and was never the intention for the concept behind Optional. Haskell for example doesn’t even have null references and still has an Option type known as the Maybe type.

To fully understand the concept of Option it is useful to look into languages that had a type like Optional long before it was available in Java.

Working with non-existing values

Generally, there are two types of concepts that support working with non-existing values: Option and Nullable Types.

Languages like Scala and Haskell developed an Option type of some sort.

Kotlin, Groovy and others defined nullable types T? and a set of operators to work with these types, like the safe call operator, the null coalescing operator or the famous elvis operator.

Both approaches provide a strict distinction between nullable and non-nullable types and solve the same kind of problems:

Expressing the non-existence of values in the type system that forces the developer to actually handle non-existing values before using them.

While a flat value with type T can be used directly, values with type Option[T] cannot and need to be handled (that means “flattened”) first. The same goes for values with type T?, which cannot be assigned to variables of type T without safely dereferencing the value (e.g. by providing a default value).

A common use case for languages with option or nullable types is returning them for get calls on maps:

// Scala
def get(key: K): Option[V]
// Haskell
lookup :: Ord k => k -> Map k a -> Maybe a
// Kotlin
operator fun get(key: K): V?

Scala and Haskell return an instance of Option. Kotlin on the other hand uses a nullable type indicating that the value may not exist.

Sadly, even though Java has the Optional type, get on Java maps still return the value directly. As usual, the reason for this decision is backwards compatibility.

Working with Option

Option types were not designed with exactly the null reference in mind. Option is a much broader concept for generalizing any kind of value that evaluates to some empty value. May it be the non-existing object value “null”, some custom empty integer value “-1” or a custom boolean flag indicating that the flag has never been set to any value. Option makes chained computations very easy, each possibly returning an option value itself, which can then be flattened and composed together.

Commonly, the Option type is defined as a sum type (also called tagged union) consisting of the two distinctive representations “value” and “no value”. The following definition is taken from the scala.Option type:

sealed abstract class Option[+A] extends Product with Serializable
final case class Some[+A](value: A) extends Option[A]
case object None extends Option[Nothing]

There is either some value a or no value at all. With Option being a container type, the value inside an Option cannot be used directly. So, for example in Scala you have multiple ways to extract the value from the option type.

Direct extraction:

// getOrElse (e.g. for default values)
val host = configuration
// orElse (e.g. for fail-fast)
user = userRepository
.orElse(throw new UserNotFoundException(s"User with id ${userId} does not exist!"))

Structural extraction:

val candidate = new URI(path)
val url = Option(candidate.getScheme) match {
case None => new File(path).toURI.toURL
case Some(_) => candidate.toURL

Using pattern matching is especially useful here as it emphasizes the branches (having some value or having no value) very clearly.

Avoid pattern like the following:

val maybeUser = userRepository.findById(userId)
if (maybeUser.isDefined) {
val user = maybeUser.get


Brian Goetz regarding Java Optional.get:

“There is a get() method on Optional; we should have never called it get(). We should have called it getOrThrowSomethingHorribleIfTheThingIsEmpty() because everybody calls it thinking, ‘I am just supposed to call Optional.get()’ and they don’t realize that it completely undermines the purpose of using Optional, because it is going to throw if the Optional is empty.”

(JAX 2015 Fragen und Antworten zu Java 8 with Angelika Langer (~16:00),

Extracting the value from the Option should not be your first intention. The Scala Standard Library states the following:

The most idiomatic way to use a scala.Option instance is to treat it as a collection or monad and use map,flatMap, filter, or foreach.

The Option type is designed for mapping and composing operations together that may or may not produce values without having to fear that the encapsulated value may not be present:

val twitterMessage = userRepository

Some languages provide a short-hand syntax for these kind of operations namely the for-comprehension:

val twitterMessage = for {
user <- userRepository.findUserByEmail("")
recentPost <- twitterRepository.findMostRecentPostByUser(user)
} yield recentPost.message

The two code examples are doing exactly the same. In fact, Scala for example will compile the for-comprehension to chained calls of map, filter and flatMap.

Working with nullable types

Nullable types on the other hand were designed to handle null values with the least amount of boilerplate as possible. The focus lies on traversing through object graphs, where any field access may potentially lead to a NPE being thrown.

Nullable types can be accessed by using the safe call (or safe navigation) operator ?. and transformed by using the null-coalescing operator or elvis operator ?: (sometimes named interchangeably):

val user = userRepository.findById(userId)
// trying to reference a field directly
> error: only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type User?
// using safe navigation operator instead
> null
// providing a default value in case of null
user?.lastName ?: "Wrynn"
> Wrynn
// more reasonably throw a custom exception
throw UserNotFoundException("No user with id ${userId} found in repository!")

While Option types are thought to be used as return values only, nullable types on the other hand may also be used in fields.

// safely referencing, which returns a value or null

Similar to Option.get there exists a not-null assertion operator for nullable types that will obviously throw a NPE when the value is null and should therefore be used carefully. Depending on the language the operator is ! (TypeScript) or !! (Kotlin).

// unsafe referencing, which returns a value or a NPE
> kotlin.KotlinNullPointerException

Option or nullable type?

There’s always a wild discussion between the followers of the two approaches about which concept is better, more concise or more functional and which not.

Option generalizes over non-existing values of any kind, not only null references. It was defined for chaining operations together, each operation possibly returning an empty value.

The focus of nullable types is making null-handling as comfortable as possible for the developer without much additional code to write.

Both concepts force the developer to check whether the value exists or not.

For me personally, the Option type feels more comfortable to work with. Although it is more verbose to handle, it provides beneficial functionality for chaining operations and flattening nested values.

Which of the two approaches do you prefer?

Thanks for reading! Feel free to comment or message me, when you have questions or suggestions.