Cloud Parts

Optional values

A type that says a value may be missing, so the compiler makes you handle “nothing” before you use it, instead of a null that crashes at run time.

How it works

In many older languages every reference can secretly be null, and using one throws (NullPointerException in Java, NullReferenceException in .NET). Optional values move “might be missing” into the type, where the compiler can see it. There are two ways to do it. A wrapper type is a separate value you must open: Option<T> in Rust (Some(x) or None), Maybe a in Haskell (Just x or Nothing), Optional<T> in Java. Rust has no null references at all; an optional pointer is Option<Box<T>>, and for such types Rust guarantees the wrapper has the same size as the plain pointer. The other way marks the type with ?: String? in Kotlin and C#, Int? in Swift, T | undefined in TypeScript with strictNullChecks. The plain type then means “never null”, and the compiler only lets you use a ? value after a check, a safe call (?.) or a fallback (?: in Kotlin, ?? in Swift and C#). Kotlin and TypeScript report errors; C# reports warnings, and its nullable reference types are entirely a compile-time feature, so the program runs the same. Every language keeps an escape hatch for when you know better (!! in Kotlin, ! in C#, Swift and TypeScript, unwrap in Rust, fromJust in Haskell), and each one brings back the crash if you are wrong.

Think of it as

A pickup slip from a parcel locker that says either “parcel inside” or “empty”. You read the slip before walking over. A null is a slip that always says “parcel inside”, even when the locker is empty, so you only find out when you open the door.

Reach for it when

  • A lookup or parse that can legitimately find nothing: a user by id, a config key, find on a list
  • Return types of public methods, so callers can’t forget the empty case
  • New code in Kotlin, Swift, C# or TypeScript: turn the checker on (<Nullable>enable</Nullable>, strictNullChecks) from the first commit

Think twice when

  • Java fields and method parameters: Optional was designed as a return type; use overloads or a nullable field
  • A failure that needs a reason: None can’t say what went wrong, so return a Result or Either, or throw
  • Silencing the checker with !, !! or unwrap everywhere, which brings the run-time crash back

A real example

A TypeScript app looks up the logged-in user with users.find(u => u.name === loggedInUsername) and then reads loggedInUser.age. With strictNullChecks off, find is typed as always returning a user, the code compiles, and it fails at run time when nobody matches. With it on, find returns User | undefined, and the compiler stops the build with error 18048, 'loggedInUser' is possibly 'undefined'. Adding if (loggedInUser !== undefined) before the read fixes it: inside that type guard, the type narrows to the user type.

“Maybe missing” in seven languages#

The same idea, written as a wrapper (Java, Rust, Haskell) or as a mark on the type (Kotlin, C#, TypeScript, Swift).

LanguageTypeChecked byHandle it withEscape hatch
JavaOptional<T> (since Java 8)Nothing: an Optional variable can itself be null, so the rule is that it never should bemap, flatMap, orElse, ifPresentOrElseget(), which throws NoSuchElementException when empty
KotlinString?Compiler errorsif check (smart cast), safe call ?., Elvis ?:!!, which throws a NullPointerException
C#string? (nullable reference types, C# 8)Compiler warnings; run-time behaviour unchangedNull checks and is patterns, ?., ??The null-forgiving !
TypeScriptT | undefined, T | nullCompiler errors with strictNullChecksType guards such as x !== undefinedPostfix !, removed from the emitted JavaScript
SwiftInt?Compiler errorsif let, ?., ??!, which stops the program when the value is nil
RustOption<T>Compiler errors (there are no null references)match, ?, and_thenunwrap, which panics
HaskellMaybe aCompiler errorsPattern matching, fromMaybe, do blocksfromJust, a partial function

Nullable reference types in C##

With #nullable enable, string? and string are different promises. The commented line compiles, but with warning CS8602 (“Dereference of a possibly null reference”). Run it with dotnet run optional.cs on .NET 10.

csharp
#nullable enable
using System;
using System.Collections.Generic;

var emails = new Dictionary<string, string> { ["ada"] = "ada@example.com" };

// string? says: this method may return null
string? FindEmail(string user) =>
    emails.TryGetValue(user, out var email) ? email : null;

string? bob = FindEmail("bob");
// Console.WriteLine(bob.Length);           // warning: bob may be null here
Console.WriteLine(bob?.Length ?? 0);        // 0

if (FindEmail("ada") is string ada)
{
    Console.WriteLine(ada.Length);          // 15: no warning, the check proved it non-null
}

The rest of the family#

Optional values answer “is there a value?”. Close relatives answer “and if not, why?”. All of them chain the same way; the Monads page explains how.

Result, Either

value or error

Holds a value or an error: Ok/Err in Rust, Right/Left in Haskell. Haskell’s docs call Maybe a simple error monad where every error is Nothing, and say a richer one can be built with Either. Chaining stops at the first error and carries it out.

Validation

all the errors

Cats’ Validated (Scala) looks like Either but collects every error instead of stopping at the first, which suits a form with five bad fields. The price: it isn’t a monad but an applicative functor, so it has no flatMap. For a check that needs an earlier result, Cats offers andThen, which behaves like Either’s flatMap.

Try

caught exception

Scala’s Try[T] is a computation that may have failed by throwing. It is either Success(value) or Failure(exception), which turns exception-throwing code into a value you can pass around and chain.

Nullable value types

C# int?

Some languages also mark plain numbers as optional: C# has had nullable value types (int?) since C# 2.0 in 2005, long before nullable reference types arrived in C# 8.

Where `Optional` belongs in Java#

Java’s API documentation is explicit: Optional “is primarily intended for use as a method return type where there is a clear need to represent ‘no result,’ and where using null is likely to cause errors”, and a variable of type Optional should never itself be null. Unlike Kotlin’s ?, nothing in the compiler enforces either rule.

Teams still argue about fields and parameters. Oracle’s Inside Java Newscast described the camps, from never using Optional to using it everywhere, and reported that its developers, Brian Goetz and Stuart Marks among them, recommend the middle: only return values, and only where a missing result is error-prone. The case against fields and parameters is practical: Optional isn’t serializable, callers must wrap every argument, and a caller can still pass null for the Optional itself. The newscast suggests trying overloads first, then builders or parameter objects.

In code · java

import java.util.Map;
import java.util.Optional;

public class Lookup {
    static final Map<String, String> EMAILS = Map.of("ada", "ada@example.com");

    // the return type says “there may be no result”
    static Optional<String> findEmail(String user) {
        return Optional.ofNullable(EMAILS.get(user));
    }

    public static void main(String[] args) {
        String domain = findEmail("ada")
            .map(e -> e.substring(e.indexOf('@') + 1))
            .orElse("unknown");
        System.out.println(domain);                              // example.com
        System.out.println(findEmail("bob").isPresent());        // false
        System.out.println(findEmail("bob").map(String::length).orElse(0)); // 0
    }
}

Often confused with

Where it came from1965–2019

Tony Hoare added the null reference to ALGOL W in 1965. He was designing the first comprehensive type system for references in an object-oriented language, with the goal that every use of a reference would be checked by the compiler, but, in his words, “I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement.” At QCon London in 2009 he called it his “billion-dollar mistake”: it had “led to innumerable errors, vulnerabilities, and system crashes”. His abstract noted that newer languages such as Spec# had added declarations for non-null references, “the solution, which I rejected in 1965.”

Functional languages such as Haskell offered the alternative: Maybe makes absence an ordinary value that must be matched. Mainstream languages followed in the 2010s. Java 8 (2014) added Optional for return types. Kotlin built null safety into its type system, citing the billion-dollar mistake, and reached 1.0 in 2016. Swift made nil “the absence of a value of a certain type” rather than a pointer, and Rust left null references out entirely.

Languages with a large existing codebase retrofitted the check instead. TypeScript 2.0 (September 2016) added strictNullChecks; its announcement joked that “if null is the billion dollar mistake, undefined only doubles our losses.” C# 8.0 (September 2019) added nullable reference types as warnings from static analysis, leaving run-time behaviour unchanged, so old code still compiles and teams can opt in.

Why it’s built this way

Put absence in the type, not in every reference
Hoare’s original goal for ALGOL W was that all use of references should be checked automatically by the compiler. A null that fits every reference type defeats that check; a separate type (Option, String?) restores it.
A ? on the type rather than a wrapper object
Kotlin, C#, Swift and TypeScript had to interoperate with code and libraries that already use null. Marking the type keeps the same values at run time: TypeScript erases its ! from the emitted JavaScript, and C#’s analysis is compile-time only.
Warnings, not errors, in C#
C# added the feature 17 years after 1.0. Making it static analysis with warnings means existing code keeps compiling and running the same, and a project opts in with <Nullable>enable</Nullable>.
Java’s Optional only as a return type
It was added as a library class, not a language feature, so it can’t stop a null Optional. Its docs scope it to return values where null is likely to cause errors.

Timeline

  1. 1965

    Hoare adds the null reference to ALGOL W

  2. 2009

    Hoare’s talk “Null References: The Billion Dollar Mistake” at QCon London

  3. 2014

    Java 8 ships java.util.Optional

  4. 2016

    Kotlin 1.0 released with null safety; TypeScript 2.0 adds strictNullChecks

  5. 2019

    C# 8.0 adds nullable reference types

Sources13

Improve this page on GitHub