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.
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,
findon 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:
Optionalwas designed as a return type; use overloads or a nullable field - A failure that needs a reason:
Nonecan’t say what went wrong, so return aResultorEither, or throw - Silencing the checker with
!,!!orunwrapeverywhere, 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).
| Language | Type | Checked by | Handle it with | Escape hatch |
|---|---|---|---|---|
| Java | Optional<T> (since Java 8) | Nothing: an Optional variable can itself be null, so the rule is that it never should be | map, flatMap, orElse, ifPresentOrElse | get(), which throws NoSuchElementException when empty |
| Kotlin | String? | Compiler errors | if check (smart cast), safe call ?., Elvis ?: | !!, which throws a NullPointerException |
| C# | string? (nullable reference types, C# 8) | Compiler warnings; run-time behaviour unchanged | Null checks and is patterns, ?., ?? | The null-forgiving ! |
| TypeScript | T | undefined, T | null | Compiler errors with strictNullChecks | Type guards such as x !== undefined | Postfix !, removed from the emitted JavaScript |
| Swift | Int? | Compiler errors | if let, ?., ?? | !, which stops the program when the value is nil |
| Rust | Option<T> | Compiler errors (there are no null references) | match, ?, and_then | unwrap, which panics |
| Haskell | Maybe a | Compiler errors | Pattern matching, fromMaybe, do blocks | fromJust, 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.
#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 errorHolds 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 errorsCats’ 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 exceptionScala’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
Optionalonly as a return type - It was added as a library class, not a language feature, so it can’t stop a
nullOptional. Its docs scope it to return values wherenullis likely to cause errors.
Timeline
- 1965
Hoare adds the null reference to ALGOL W
- 2009
Hoare’s talk “Null References: The Billion Dollar Mistake” at QCon London
- 2014
Java 8 ships
java.util.Optional - 2016
Kotlin 1.0 released with null safety; TypeScript 2.0 adds
strictNullChecks - 2019
C# 8.0 adds nullable reference types
- QCon London 2009: Tony Hoare, Null References: The Billion Dollar Mistake (abstract)
- InfoQ: Tony Hoare, Null References: The Billion Dollar Mistake (recorded Aug 2009)
- Haskell base library: Data.Maybe
- Java SE 21 API: Optional (since 1.8; API note on intended use)
- OpenJDK: JDK 8 (General Availability on 18 March 2014)
- JetBrains blog: Kotlin 1.0 Released (Feb 2016)
- Kotlin docs: Null safety
- The Swift Programming Language: The Basics (Optionals; nil is not a pointer)
- Microsoft devblogs: Announcing TypeScript 2.0 (Sep 2016)
- TypeScript 2.0 release notes (strictNullChecks, non-null assertion operator)
- Microsoft Learn: The history of C# (C# 8.0 released September 2019; C# 2.0 November 2005)
- Microsoft Learn: Nullable reference types
Sources13
- Java SE 21 API: Optional (since 1.8; API note on intended use)
- Inside Java Newscast #19: Where to use Optional (Nicolai Parlog, Oracle)
- Kotlin docs: Null safety
- Microsoft Learn: Nullable reference types
- Microsoft Learn: The history of C# (nullable value types in C# 2.0; nullable reference types in C# 8.0)
- TypeScript TSConfig reference: strictNullChecks (error 18048 example)
- TypeScript 2.0 release notes (strictNullChecks, type guards, non-null assertion operator)
- The Swift Programming Language: The Basics (Optionals, if let, ??, force unwrapping)
- Rust std docs: module std::option (no null references; representation guarantees; ?)
- Haskell base library: Data.Maybe (Maybe as a simple error monad; fromMaybe, fromJust)
- Scala 3 API: scala.util.Try
- Typelevel Cats docs: Validated (accumulating errors; not a monad)
- QCon London 2009: Tony Hoare, Null References: The Billion Dollar Mistake (abstract)