Null Safety and the Billion-Dollar Mistake
Tony Hoare spent one sentence apologizing for the null reference and the software industry has spent six decades cleaning up after it. Kotlin, Java, and Rust each answer his mistake with a genuinely different design, not just a different syntax for the same idea.
Sir Tony Hoare passed away on March 5, 2026, at the age of 92. Most obituaries led with Quicksort, and rightly so, but the line everyone actually quoted was the one he gave in 2009 at QCon London, admitting to something almost no language designer ever admits to in public. It is worth revisiting that admission directly, because the industry’s response to it is still evolving, and three of today’s most-used typed languages have landed on three distinct answers.
What Hoare Actually Said
Speaking about his work on ALGOL W in 1965, Hoare described building the first comprehensive reference type system for an object-oriented language, one designed so that every reference use would be checked automatically by the compiler and therefore safe by construction. Then he added the null reference anyway, by his own account simply because it was the easiest thing to implement.
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
What rarely makes it into the shorter retellings is the commercial pressure behind the decision. According to a retrospective published in JAVAPRO International, Hoare already knew of an alternative at the time, a disjoint union that would have forced every absent value to be handled explicitly. His employer wanted to sell computers, and customers wanted their existing Fortran programs to keep running rather than wait for a fix. Null shipped because it was fast, not because it was the only option. ALGOL’s influence then rippled forward through Simula, C, C++, and eventually Java, carrying the null reference along with it into nearly every mainstream language that followed.
What That Decision Still Costs
The most-cited hard number on this comes from Harness, the company formerly known as OverOps, which mined a billion logged errors across more than a thousand production Java applications and found that ten exception types accounted for 97 percent of everything logged. NullPointerException topped the list by a wide margin.

Three of the top five, NullPointerException, NumberFormatException, and IllegalArgumentException, all trace back to the same root habit: passing a value through several layers of code without the type system ever forcing a check on whether it is actually there. The CWE Top 25 Most Dangerous Software Weaknesses list, which tracks the most consequential security flaws each year, has kept a null pointer dereference entry in its ranking every year recently, moving between 11th and 21st place from 2020 through 2024. It is not usually the most severe entry on the list, but it is one of the most persistent.
A separate, more encouraging data point comes from Uber’s NullAway research, a type-based null checker built for large Java codebases. Uber’s engineers examined production crash data from Android apps already built with NullAway and asked where the remaining null pointer exceptions were still slipping through.

None of the surviving crashes were caused by a false assumption baked into the checker itself. Every one came from the boundary of the checked code: a third-party library the checker could not see into, a deliberate suppression a developer wrote to silence a warning, or reflection and other code paths that modify objects after the checker has already looked at them. That is a useful way to think about every null-safety design that follows: the question is never whether a language can eliminate null pointer errors inside its own guarantees, but how much of your actual codebase those guarantees reach.
Three Different Places to Put the Guarantee
Kotlin: Absence Lives in the Type Itself
Kotlin’s answer, non-nullable types by default, treats nullability as a property of the type rather than a runtime state to check for. String and String? are different types, and the compiler will not let a nullable value flow into a slot that expects a non-null one without an explicit check, a safe call, or an elvis operator to supply a fallback. The escape hatch is platform types, the special notation Kotlin assigns to values coming from Java code, where the compiler cannot know whether the Java side promises non-null and simply trusts the developer. That interop boundary is exactly where Kotlin’s guarantee stops, in the same way NullAway’s guarantee stops at the edge of unchecked libraries.
Java’s Optional: A Return-Type Signal, Not a Null Replacement
Optional arrived in Java 8 with a narrower job than most developers assume. Brian Goetz, Java’s language architect, has been explicit that the type was designed for method return values, essentially OptionalReturn<T> in spirit, to give an API a fluent way of saying a result might be absent. It was never intended for fields, constructor parameters, or method arguments, and it is not Serializable for that reason. Critically, an Optional<T> reference can itself be null, so Optional narrows where null can hide without removing the concept from the language. Retrofitting it onto an existing codebase changes call sites, not the underlying type system.
Rust’s Option: No Null Anywhere in the Language
Rust takes the most structural approach of the three. There is no null keyword in safe Rust at all. Absence is represented by the Option<T> enum, with exactly two variants, Some(T) and None, and the compiler requires every match against it to handle both. Combinator methods let you chain operations without manually unwrapping at each step, and the language gives you no way to accidentally treat a None as if it were a value, short of calling an explicitly named unwrapping method that panics if you were wrong. The guarantee holds everywhere in safe Rust; it only breaks down inside code marked unsafe, which exists precisely to mark the boundary where the compiler’s guarantees no longer apply.
| Design | Where absence lives | Who enforces it | Main escape hatch |
|---|---|---|---|
| Kotlin nullable types | Baked into the type itself (T vs T?) | Compiler, at every assignment and call site | Platform types from Java interop |
Java Optional<T> | A wrapper type returned by methods that choose to use it | Convention and API design, not the compiler | Optional reference itself can be null; not enforced on fields or parameters |
Rust Option<T> | An enum with two variants, part of the core language | Compiler, via exhaustive pattern matching | Explicit unsafe blocks or an intentional unwrap panic |
What actually changes when a team adopts one of these
- Kotlin’s win is retroactive safety at interop boundaries, not just new code, since the compiler tracks nullability through the whole call graph you write.
- Optional’s win is clearer API contracts at method boundaries, at the cost of leaving fields, parameters, and legacy code exactly as null-prone as before.
- Rust’s win is the strongest guarantee of the three, but it only applies to code the compiler can see; FFI and unsafe blocks reopen the same door every other language leaves open by default.
- None of the three, on their own, catch a third-party dependency that hands you a null or an Option it never validated, which is the same boundary problem NullAway’s data surfaced.
The Pattern Underneath the Differences
Line them up and a common shape appears. Kotlin makes absence part of every type and enforces it at the compiler level, but concedes the boundary with Java. Rust makes absence a data type with no separate null concept anywhere in safe code, but concedes the boundary at unsafe blocks and foreign function calls. Java’s Optional makes almost no structural promise at all; it is a convention that well-designed APIs opt into, and it leaves every field and parameter exactly as exposed as they always were. The billion-dollar mistake was never really about the keyword null. It was about handing developers a way to skip a check, and sixty years later, the difference between these three languages comes down to how hard each one makes that skip.
What We Learned
Tony Hoare’s 2009 QCon confession traced the null reference back to a 1965 shortcut in ALGOL W, one he knew of an alternative to at the time and took anyway under commercial pressure. That shortcut still shows up at the top of production error logs six decades later, with NullPointerException appearing in 70 percent of the environments Harness studied across a billion logged errors. Kotlin, Java, and Rust each respond to that legacy differently: Kotlin bakes non-nullability into the type system with the compiler enforcing it end to end, Java’s Optional offers a narrower, opt-in signal meant for return values rather than a structural fix, and Rust replaces null entirely with an enum that the compiler forces you to match exhaustively.
Uber’s NullAway research is the clearest reminder that none of these guarantees are absolute: they hold inside the code the compiler can see, and every one of them still leaves a boundary, whether that is Java interop, an unchecked field, or an unsafe block, where the old mistake can walk back in.






