Introducing Optional
Introducing Optional
In the previous lesson we saw how null leads to NullPointerException, unclear contracts, and defensive code scattered everywhere. Java 8 introduced java.util.Optional<T> as a typed container that explicitly represents the possibility of absence. This lesson covers the three factory methods you use to create an Optional and the basic ways to ask whether it holds a value.
What Optional Is — and Is Not
Optional<T> is a value-type wrapper. It has exactly two states:
- Present — it wraps a non-null value of type
T. - Empty — it holds no value at all (analogous to
null, but explicit).
Three Factory Methods
Optional.of — when you are certain a value exists
Optional.of(value) wraps a value that you know is non-null. If you pass null it throws NullPointerException immediately, which is intentional — it fails fast at the construction site rather than silently propagating.
Use Optional.of when the value comes from a source you control and can guarantee is non-null — for example, a constant, a freshly constructed object, or a value you just validated.
Optional.ofNullable — when the value may or may not be null
Optional.ofNullable(value) is the most common factory. It inspects the value: if it is non-null it returns a present Optional; if it is null it returns an empty Optional. This is the bridge between legacy null-returning APIs and the Optional world.
Optional.empty — an explicit empty Optional
Optional.empty() creates an empty container with no value. You use it in methods that return Optional<T> when there is nothing to return — it is the typed, intentional replacement for return null.
Returning Optional.empty() is far more honest than returning null: the compiler enforces that callers handle a Optional<String> return type, and no annotation or comment is needed to communicate absence.
Checking Presence
Once you hold an Optional you can ask whether it contains a value using three related methods:
isPresent()— returnstrueif a value is present.isEmpty()— returnstrueif the container is empty (added in Java 11; the readable inverse ofisPresent()).get()— returns the wrapped value, but throwsNoSuchElementExceptionif empty.
get() in production code. Calling get() without a prior isPresent() check is exactly as dangerous as dereferencing a potentially-null reference — you just traded NullPointerException for NoSuchElementException. In the next lessons you will learn safer extraction methods (orElse, orElseGet, map, ifPresent) that express intent more cleanly and cannot throw.
Equality and toString
Optional overrides equals and hashCode by delegating to the wrapped value, so two present Optionals with equal values are equal, and two empty Optionals are always equal to each other.
Choosing the Right Factory
A simple decision tree:
- You control the value and it can never be null →
Optional.of(value) - The value comes from a legacy API or user input that might be null →
Optional.ofNullable(value) - You are writing a method and have no result to return →
Optional.empty()
ofNullable when in doubt. If you are not 100% sure a value is non-null, use ofNullable. The extra null-check costs nothing measurable, and the fail-fast of of is only useful when you actually want to crash loudly on a contract violation — for example, in constructor validation.
Summary
Optional.of wraps a guaranteed-non-null value and fails fast on null. Optional.ofNullable wraps anything, silently converting null to empty. Optional.empty() represents an explicit no-value return. To inspect the state use isPresent() or isEmpty() (Java 11+), and treat bare get() as a last resort that requires a guard. In the next lesson you will see the richer extraction and transformation API — orElse, orElseGet, orElseThrow — that makes Optional genuinely useful rather than just a renamed null check.