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)
1jshell> Optional.ofNullable(null)2$1 ==> Optional.empty
1jshell> Optional.ofNullable("123")2$2 ==> Optional[123]
Optional#of(T)
1jshell> Optional.of("123")2$3 ==> Optional[123]
1jshell> Optional.of(null)2| 例外java.lang.NullPointerException3| at Objects.requireNonNull (Objects.java:209)4| at Optional.of (Optional.java:113)5| at (#5:1)
Optional#empty()
1jshell> Optional.empty()2$5 ==> 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).
1jshell> Integer.parseInt("123")2$6 ==> 123
If the string is not an integer, what happens?
Integer#parseInt(String) throws an unchecked exception NumberFormatException.
1jshell> Integer.parseInt("abc")2| 例外java.lang.NumberFormatException: For input string: "abc"3| at NumberFormatException.forInputString (NumberFormatException.java:67)4| at Integer.parseInt (Integer.java:668)5| at Integer.parseInt (Integer.java:786)6| at (#8:1)
So if you want to convert a string that might not be an integer, you end up writing:
1Integer number;2try {3number = Integer.parseInt(str);4} catch (NumberFormatException e) {5// Handle failure6number = ...;7}
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.
1jshell> public class NumberUtils {2...>3...> public static Integer parseInt(String str) {4...> try {5...> return Integer.parseInt(str);6...> } catch (NumberFormatException e) {7...> return null;8...> }9...> }10...> }11| 次を作成しました: クラス NumberUtils
Try it with integer and non-integer strings.
1jshell> NumberUtils.parseInt("123")2$9 ==> 123
For non-integer input, it returns null.
1jshell> NumberUtils.parseInt("abc")2$10 ==> null
Now you can write:
1Integer number = NumberUtils.parseInt(str);
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
1public class Price {23public static String taxIncluded(String price) {4// Return price with 10% tax5}6}
Now implement it. To extract the numeric part you could use regex, but here we keep it simple.
1jshell> public class Price {2...> public static String taxIncluded(String jpPrice) {3...> if (jpPrice == null) {4...> return null;5...> }6...>7...> if ("未定".equals(jpPrice)) {8...> return "未定";9...> }10...>11...> if (jpPrice.length() > 1 && jpPirce.charAt(jpPrice.length() - 1) == '円') {12...> String price = jpPrice.substring(0, jpPrice.length() - 1);13...> Integer priceInt = NumberUtils.parseInt(price);14...>15...> return ((int) Math.floor(priceInt * 1.1)) + "円";16...> }17...>18...> return null;19...> }20...> }21| 次を作成しました: クラス Price
We'll round down the tax because we're kind.3
If the format is neither 未定 nor XXX円, return null.
Does it work?
1jshell> Price.taxIncluded("1000円")2$12 ==> "1100円"34jshell> Price.taxIncluded("未定")5$13 ==> "未定"67jshell> Price.taxIncluded(null)8$14 ==> null910jshell> Price.taxIncluded("1,000円")11| 例外java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because "<local2>" is null12| at Price.taxIncluded (#18:15)13| at (#20:1)
Oops 😵
NumberUtils#parseInt(String) returns null when conversion fails.
We forgot to handle that, so fix it.
1jshell> public class Price {2...> public static String taxIncluded(String jpPrice) {3...> if (jpPrice == null) {4...> return null;5...> }6...>7...> if ("未定".equals(jpPrice)) {8...> return "未定";9...> }10...>11...> if (jpPrice.length() > 1 && jpPrice.charAt(jpPrice.length() - 1) == '円') {12...> String price = jpPrice.substring(0, jpPrice.length() - 1);13...> Integer priceInt = NumberUtils.parseInt(price);14...>15...> if (priceInt == null) {16...> return null;17...> }18...>19...> return ((int) Math.floor(priceInt * 1.1)) + "円";20...> }21...>22...> return null;23...> }24...> }25| 次を変更しました: クラス Price
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.
1jshell> public class NumberUtils {2...>3...> public static Optional<Integer> parseIntOptional(String str) {4...> try {5...> return Optional.of(Integer.parseInt(str));6...> } catch (NumberFormatException e) {7...> return Optional.empty();8...> }9...> }10...> }11| 次を置換しました: クラス NumberUtils
With Optional, return Optional.empty() instead of null, and Optional.of(T) on success.
Using NumberUtils#parseIntOptional(String):
1jshell> NumberUtils.parseIntOptional("abc")2$18 ==> Optional.empty34jshell> NumberUtils.parseIntOptional("123")5$19 ==> Optional[123]
Now let's write Price#taxIncluded(String) using this and Optional.
Different people may write it differently, but here's one approach.
1jshell> public class Price {2...> public static String taxIncluded(String jpPrice) {3...> return Optional.ofNullable(jpPrice)4...> .flatMap(str -> {5...> if (str.equals("未定")) {6...> return Optional.of("未定");7...> }8...>9...> return Optional.of(str)10...> .filter(it -> it.length() > 1 && it.charAt(it.length() - 1) == '円')11...> .map(it -> it.substring(0, it.length() - 1))12...> .flatMap(NumberUtils::parseIntOptional)13...> .map(it -> (int) (it * 1.1))14...> .map(it -> it + "円");15...> })16...> .orElse(null);17...> }18...> }19| 次を作成しました: クラス Price
Alternatively, you can pull the 未定 logic outside flatMap:
1public class Price {2public static String taxIncluded(String jpPrice) {3if ("未定".equals(jpPrice)) {4return "未定";5}67return Optional.ofNullable(jpPrice)8.flatMap(str ->9Optional.of(str)10.filter(it -> it.length() > 1 && it.charAt(it.length() - 1) == '円')11.map(it -> it.substring(0, it.length() - 1))12.flatMap(NumberUtils::parseIntOptional)13.map(it -> (int) (it * 1.1))14.map(it -> it + "円"))15.orElse(null);16}17}
Test it:
1jshell> Price.taxIncluded("1000円")2$21 ==> "1100円"34jshell> Price.taxIncluded("未定")5$22 ==> "未定"67jshell> Price.taxIncluded(null)8$23 ==> null910jshell> Price.taxIncluded("1,000円")11$24 ==> null
Now compare the code with and without Optional.
1public class Price {2public static String taxIncluded(String jpPrice) {3if (jpPrice == null) {4return null;5}67if ("未定".equals(jpPrice)) {8return "未定";9}1011if (jpPrice.length() > 1 && jpPrice.charAt(jpPrice.length() - 1) == '円') {12String price = jpPrice.substring(0, jpPrice.length() - 1);13Integer priceInt = NumberUtils.parseInt(price);1415if (priceInt == null) {16return null;17}1819return ((int) Math.floor(priceInt * 1.1)) + "円";20}2122return null;23}24}
1public class Price {2public static String taxIncluded(String jpPrice) {3if ("未定".equals(jpPrice)) {4return "未定";5}67return Optional.ofNullable(jpPrice)8.flatMap(str ->9Optional.of(str)10.filter(it -> it.length() > 1 && it.charAt(it.length() - 1) == '円')11.map(it -> it.substring(0, it.length() - 1))12.flatMap(NumberUtils::parseIntOptional)13.map(it -> (int) (it * 1.1))14.map(it -> it + "円"))15.orElse(null);16}17}
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?
1jshell> public class Price {2...> public static Optional<String> taxIncluded(String jpPrice) {3...> if ("未定".equals(jpPrice)) {4...> return Optional.of("未定");5...> }6...>7...> return Optional.ofNullable(jpPrice)8...> .flatMap(str ->9...> Optional.of(str)10...> .filter(it -> it.length() > 1 && it.charAt(it.length() - 1) == '円')11...> .map(it -> it.substring(0, it.length() - 1))12...> .flatMap(NumberUtils::parseIntOptional)13...> .map(it -> (int) (it * 1.1))14...> .map(it -> it + "円"));15...> }16...> }17| 次を置換しました: クラス Price
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.
1public class Price {2public static Optional<String> taxIncluded(String jpPrice) {3if ("未定".equals(jpPrice)) {4return Optional.of("未定");5}67return Optional.ofNullable(jpPrice)8.flatMap(str ->9Optional.of(str)10.filter(it -> it.length() > 1 && it.charAt(it.length() - 1) == '円')11.map(it -> it.substring(0, it.length() - 1))12.flatMap(NumberUtils::parseIntOptional)13.map(it -> (int) (it * 1.1))14.map(it -> it + "円"));15}16}
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.
1public class Price {2public static String taxIncluded(String jpPrice) {3if (jpPrice == null) {4return null;5}67if ("未定".equals(jpPrice)) {8return "未定";9}1011if (jpPrice.length() > 1 && jpPrice.charAt(jpPrice.length() - 1) == '円') {12String price = jpPrice.substring(0, jpPrice.length() - 1);13Integer priceInt = NumberUtils.parseInt(price);1415if (priceInt == null) {16return null;17}1819return ((int) Math.floor(priceInt * 1.1)) + "円";20}2122return null;23}24}
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 😡 ↩

