2024-09-08

About Optional

Programming
Notes
This article was translated by GPT-5.2-Codex. The original is here.

Introduction

Understand the design philosophy! 1

TL;DR

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 jpPrice is null
  • When priceInt is null

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.

{7-14}java

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.

{15-17,22}java

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 Optional makes it clear the method may fail
  • Returning Optional forces callers to handle the absence of values, improving null safety5
  • Wrapping with Optional allows 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
  • Optional is 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.

Footnotes

  1. "メディアの違いを理解せよ!" - Nicovideo Encyclopedia

  2. I can hear the voices saying "You implement tax logic in a static method?".

  3. (Q9) How should we handle rounding when setting tax-included prices?

  4. In languages without null, types like Optional (e.g., Haskell's Maybe) are often explained as representing success/failure of computations.

  5. In practice, people unfamiliar with Optional often call Optional#get() and trigger runtime exceptions 😡

Effective Java 第3版

Effective Java 第3版

Javaによる関数型プログラミング ―Java 8ラムダ式とStream

Javaによる関数型プログラミング ―Java 8ラムダ式とStream

Amazon アソシエイトについて

この記事には Amazon アソシエイトのリンクが含まれています。Amazonのアソシエイトとして、SuzumiyaAoba は適格販売により収入を得ています。