Notes
Introduction
Understand the design philosophy! 1
TL;DR
- Use
Optionaleffectively - Understand the design philosophy of
Optional - Read this article or 12 recipes for using the Optional class as it is meant to be used
Optional
Optional was introduced to the Java standard library in Java 8.
Optional represents a value that either exists or does not exist.
In Java, "no value" means null.
Before Java 8, when a variable had no value to assign, you represented that by assigning null.
With Optional, the official way is to represent "no value" with an object that represents the absence of a value.
The idea of representing a value that might not exist with a data structure is not new.
Many languages, starting with Haskell, provide it in their standard libraries.
I don't know whether it existed as early as Miranda, a predecessor of Haskell, but by the 1990s, there were already references to the Maybe type constructor.
In other words, a concept from the 1990s finally made it into Java.
Haskell itself was released in 1990, before Java (1995).
Optional instances
To create a value wrapped in Optional, call one of these static methods:
Each has a different purpose, so let's check them one by one.
Optional#ofNullable(T)
Optional#of(T)
Optional#empty()
How to use Optional
Example using Optional
To understand Optional, let's consider converting a string to an integer.
This is a simple example of a computation that can fail.
If the string is a valid integer format, we expect an Integer value.
In Java, it is common to use Integer#parseInt(String).
If the string is not an integer, what happens?
Integer#parseInt(String) throws an unchecked exception NumberFormatException.
So if you want to convert a string that might not be an integer, you end up writing:
Writing this every time is tedious, so you'd want a utility.
Here we consider two implementations: one without Optional, and one with Optional.
Represent failure with null
In Java, if a computation fails, it's common to throw an exception or return null.
Integer#parseInt(String) throws, but then you must always catch it.
So let's write a utility method that returns null instead of throwing.
Try it with integer and non-integer strings.
For non-integer input, it returns null.
Now you can write:
This looks nicer than try/catch.
But is it really safe?
Let's consider a slightly more complex example: given a tax-excluded price string, compute a tax-included price (10%).
More precisely:
- Price is given in the format
XXX円 - If price is unknown, the string
未定is given - If price is known, return the price with 10% tax added
- If price is unknown, return
未定 - If the format is invalid, return something else
Let's define a static method for this.2
Now implement it. To extract the numeric part you could use regex, but here we keep it simple.
We'll round down the tax because we're kind.3
If the format is neither 未定 nor XXX円, return null.
Does it work?
Oops 😵
NumberUtils#parseInt(String) returns null when conversion fails.
We forgot to handle that, so fix it.
This is fine now. But the code contains things we don't want to care about in the main logic. Specifically:
- When
jpPriceisnull - When
priceIntisnull
Here we intentionally wrote it this way, but we forgot NumberUtils#parseInt(String) returns null and caused a NullPointerException.
Now let's rewrite this using Optional.
Using Optional
Finally, the main topic: using Optional.
Let's see the code. As with the non-Optional version, start with NumberUtils.
With Optional, return Optional.empty() instead of null, and Optional.of(T) on success.
Using NumberUtils#parseIntOptional(String):
Now let's write Price#taxIncluded(String) using this and Optional.
Different people may write it differently, but here's one approach.
Alternatively, you can pull the 未定 logic outside flatMap:
Test it:
Now compare the code with and without Optional.
The Optional version is shorter in lines, but that's not the point.
The key is that the Optional version focuses on what we want to do (convert string to int and compute tax) without worrying about null along the way.
In the non-Optional version, we forgot NumberUtils#parseInt(String) can return null and got a NullPointerException.
With Optional, if conversion fails, Optional.empty() is returned, so no NullPointerException.
If a computation returns Optional, you can flatten it with flatMap.
Then you can write downstream processing in map, which only runs on success.
This means you can consider failure only at the end (Optional#orElse(T)), not at every step.
In this requirement, if computation fails, return "something else", so I returned null,
but I was following the non-Optional example. If failure is possible, why not return Optional itself?
If you think, "Returning null is fine," you might have forgotten the earlier mistake with NumberUtils#parseInt(String).
When using that static method, are you confident you will always remember to null-check the return value?
Optional represents computations that may fail
We've compared code with and without Optional.
Now let's look at the Javadoc again.
A container object which may or may not contain a non-null value.
...
API Note: Optional is intended primarily 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. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.
As an object, Optional is "a container that may or may not contain a non-null value."
But if we return Optional.empty() on failure and Optional.of(T) on success,
it becomes not just "some value or not", but a way to express success/failure.4
With Optional, you can write code centered on success, and consider recovery only at the end.
Let's re-examine the tax calculation with that in mind.
In the Optional version, only the success path is highlighted, and failure branches are not explicit.
You can focus on success without explicit null checks.
In contrast, the non-Optional version has failure handling in the main flow, fragmenting the tax logic.
This shows that Optional lets you keep the intended logic together.
In this example, failure was only possible for null input or invalid formats,
but even with more possible failures, Optional can keep the code focused on the desired processing.
Benefits of using Optional
There are three main benefits:
- Returning
Optionalmakes it clear the method may fail - Returning
Optionalforces callers to handle the absence of values, improvingnullsafety5 - Wrapping with
Optionalallows you to ignore failure paths until the end
Problems with Optional
So far I've described the good parts, but Optional also has issues.
- It can express success/failure but not the reason for failure
- Using
Optional-style code is still not mainstream Optionalis neither a monad nor a functor
It expresses success/failure but not the reason
Optional can return a value on success, but all failures collapse into Optional.empty().
So you cannot handle different failure reasons differently.
To express failure reasons, you usually need types like Either (Left, Right) or Result (Ok, Err).
In Either, Right holds the success value (right is "correct"), and Left holds the failure value.
Then you can return error values and still use methods like map and filter that operate on Right only.
I hope Java will provide something like this in the standard library one day, but as of Java 21 it does not. The most practical option is to use a functional library like vavr, but introducing such a library in production is hard.
It's partly a skill issue, but minor libraries also carry a maintenance risk.
Optional-style code is not mainstream
Optional is neither a monad nor a functor
Rules when using Optional
Conclusion
I explained the basic usage and properties of Optional.
I hope the world sees at least one fewer NullPointerException. I’ll stop here.
Related articles
- 12 recipes for using the Optional class as it is meant to be used
- Team effort! Confronting Null in Java #NULL - Qiita
Footnotes
-
I can hear the voices saying "You implement tax logic in a static method?". ↩
-
(Q9) How should we handle rounding when setting tax-included prices? ↩
-
In languages without
null, types likeOptional(e.g., Haskell'sMaybe) are often explained as representing success/failure of computations. ↩ -
In practice, people unfamiliar with
Optionaloften callOptional#get()and trigger runtime exceptions 😡 ↩

