Learn Java

Lesson 55Phase 6: Functional and modern Java

Optional: a value that might be missing

Saying "there may be no result" in the type itself instead of returning null, and the Optional methods that make it pleasant.

By Updated 40 min

  • Optional
  • of
  • ofNullable
  • empty
  • orElse
  • orElseThrow

A library catalog has a method that finds a book by its id. Ask it for book 7 when there is no book 7, and what should it hand back? For a long time the usual answer in Java was null, and the usual result was a NullPointerException a few lines later. This lesson is about Optional, a small class from the Java library that puts “there may be no result” into a method's return type, where the compiler can see it, and about the methods that make the missing case quick to handle.

By the end of this lesson
  • Explain why returning null for “nothing found” leads to a NullPointerException, and how an Optional return type prevents it.
  • Make an Optional with of, ofNullable and empty, and say which one fits a given value.
  • Get the value out with orElse, orElseGet and orElseThrow, and explain why orElse always runs its argument.
  • Change and test the value inside with map, filter and flatMap, and act on it with ifPresent, ifPresentOrElse, or and stream.
  • Recognize the stream methods that return an Optional, an OptionalInt or an OptionalDouble, such as findFirst, max and average.
  • Use Optional as a return type and almost nowhere else, and never call get() without a check.
From before
You know null from the this keyword and object references: a reference that points at no object, and the NullPointerException you get when you use the dot on it. In HashMap and HashSet, get returned null for a key the map did not have. In generics you finished a Box<T> with isEmpty and getOrElse, a box that may hold nothing. And Optional has appeared already: in streams, findFirst() returned one and orElse took the value out. The stream flatMap from Collectors and stream recipes comes back near the end.

When a method finds nothing#

Here is a small library catalog. findById walks the list of books and returns the one whose id matches. When no book matches, the loop ends without returning, and the method returns null, the value that means “no object”. Run it and read the error.

FindById.java
1import java.util.List;
2
3public class Main {
4 public static void main(String[] args) {
5 Library library = new Library();
6
7 System.out.println(library.findById(2).title());
8 System.out.println(library.findById(7).title());
9 }
10}
11
12class Library {
13 private final List<Book> books = List.of(
14 new Book(1, "Emma"),
15 new Book(2, "Dune"),
16 new Book(3, "Beloved"));
17
18 Book findById(int id) {
19 for (Book book : books) {
20 if (book.id() == id) {
21 return book;
22 }
23 }
24 return null;
25 }
26}
27
28record Book(int id, String title) {}
Prints
›Dune
!Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Book.title()" because the return value of "Library.findById(int)" is null
! at Main.main(Main.java:8)
Stops on line 8

NullPointerException: Cannot invoke "Book.title()" because the return value of "Library.findById(int)" is null

A variable held null, so there was no object to use.

The first lookup prints Dune. The second stops the program on line 8 with a NullPointerException and this message:

Cannot invoke "Book.title()" because the return value of "Library.findById(int)" is null

Read it from the end. findById(7) returned null, and the program then asked that null for its title(). There was no book to ask, so Java stopped. Reading errors and debugging takes messages like this one apart piece by piece.

A search that finds nothing has still done its job. The problem is that the line Book findById(int id) gives no hint that it can happen. The return type says Book, so anyone who calls the method without reading its body expects a book. The compiler cannot warn them either, because to the compiler null is an acceptable value for any Book variable. Every caller has to remember to check for null, every time, and one forgotten check is a crash, often far from the method that returned the null.

Methods that work something out run into the same question. What is the longest word in a list? For an empty list there is no answer. Returning "" would claim there was a word with no letters in it, and returning null sets the same trap as findById.

Optional puts “maybe nothing” in the type#

Optional<T>, from the package java.util, is an object that holds either one value of type T or nothing at all. An Optional with a value inside is present; one with nothing inside is empty. Picture a small box with room for one thing. You always get the box, so you can always look inside it safely, and the label on the outside warns you that it may be empty.

Here is findById again, now returning an Optional<Book>. Two lines changed: it returns Optional.of(book) when it finds a match, and Optional.empty() when the loop ends without one.

FindOptional.java
1import java.util.List;
2import java.util.Optional;
3
4public class Main {
5 public static void main(String[] args) {
6 Library library = new Library();
7
8 Optional<Book> found = library.findById(2);
9 Optional<Book> missing = library.findById(7);
10
11 System.out.println(found);
12 System.out.println(missing);
13 }
14}
15
16class Library {
17 private final List<Book> books = List.of(
18 new Book(1, "Emma"),
19 new Book(2, "Dune"),
20 new Book(3, "Beloved"));
21
22 Optional<Book> findById(int id) {
23 for (Book book : books) {
24 if (book.id() == id) {
25 return Optional.of(book);
26 }
27 }
28 return Optional.empty();
29 }
30}
31
32record Book(int id, String title) {}
Prints
›Optional[Book[id=2, title=Dune]]
›Optional.empty

It prints Optional[Book[id=2, title=Dune]] and then Optional.empty. Printing an Optional shows its value in square brackets, or Optional.empty when there is none. The method never returns null now. Every caller gets an Optional object back, and an empty one answers questions like any other object.

The compiler now reminds you

Try the line that crashed before, library.findById(7).title(), against the new method. It does not compile:

Main.java
read along
1import java.util.List;
2import java.util.Optional;
3
4public class Main {
5 public static void main(String[] args) {
6 Library library = new Library();
7
8 System.out.println(library.findById(7).title());
9 }
10}
11
12class Library {
13 private final List<Book> books = List.of(
14 new Book(1, "Emma"),
15 new Book(2, "Dune"),
16 new Book(3, "Beloved"));
17
18 Optional<Book> findById(int id) {
19 for (Book book : books) {
20 if (book.id() == id) {
21 return Optional.of(book);
22 }
23 }
24 return Optional.empty();
25 }
26}
27
28record Book(int id, String title) {}
Terminal
read along
1$ javac Main.java
2Main.java:8: error: cannot find symbol
3 System.out.println(library.findById(7).title());
4 ^
5 symbol: method title()
6 location: class Optional<Book>
71 error

The compiler looked for a method title() on the type that findById returns, Optional<Book>, and found none. title() belongs to Book, and the book is inside the box. To reach it you call one of the methods on this page, and almost all of them make you decide, right there, what happens when the box is empty. The crash from the last section has become a compile error, which you see before the program ever runs.

Key idea

An Optional return type tells every caller, in the type itself, that there may be no result. The compiler makes them open the box before they can use the value, and that is the moment to decide what happens when it is empty.

Coming from another language?
Kotlin and Swift build this idea into the language: a String? may be null and a String may not. Rust has Option and Haskell has Maybe. In Python's type hints, Optional[str] means “a str or None”, a note for checking tools. Java's Optional is a plain class: the compiler checks how you use it, but it cannot stop a method from returning null where an Optional was promised.

Making an Optional: of, empty and ofNullable

Optional has no public constructor. You make one with one of three static methods:

  • Optional.of(value) wraps a value that is not null.
  • Optional.empty() gives an empty Optional.
  • Optional.ofNullable(value) wraps a value that might be null. It gives an empty Optional for null and a present one for anything else.

ofNullable is the bridge from older methods that answer with null, such as a map's get. Here the map loans records who has borrowed which book.

MakingOptionals.java
1import java.util.HashMap;
2import java.util.Map;
3import java.util.Optional;
4
5public class Main {
6 public static void main(String[] args) {
7 Map<Integer, String> loans = new HashMap<>();
8 loans.put(2, "Grace");
9 loans.put(3, "Alan");
10
11 Optional<String> title = Optional.of("Dune");
12 Optional<String> none = Optional.empty();
13 Optional<String> borrower2 = Optional.ofNullable(loans.get(2));
14 Optional<String> borrower1 = Optional.ofNullable(loans.get(1));
15
16 System.out.println(title + " " + none);
17 System.out.println(borrower2 + " " + borrower1);
18
19 Optional<String> broken = Optional.of(loans.get(1));
20 System.out.println(broken);
21 }
22}
Prints
›Optional[Dune] Optional.empty
›Optional[Grace] Optional.empty
!Exception in thread "main" java.lang.NullPointerException
! at Main.main(Main.java:19)
Stops on line 19

NullPointerException

A variable held null, so there was no object to use.

The first two lines print Optional[Dune] Optional.empty and Optional[Grace] Optional.empty. Nobody has borrowed book 1, so loans.get(1) returned null, and ofNullable turned that null into an empty Optional. Line 19 passes the same null to Optional.of, which refuses it straight away: the program stops with a java.lang.NullPointerException that has no message.

Which one to use
Use Optional.of for a value you know is there, such as a book you have just found in a list. If you are wrong, it fails on that very line, where the mistake is easy to find. Use ofNullable for a value from code that may answer null, and empty()to say “nothing found”.
Quick check
Nobody has borrowed book 1, so loans.get(1) returns null. What does Optional.of(loans.get(1)) do?

Writing a method that may find nothing

A method like this has the same shape every time: return Optional.of(...) at the point where you find the answer, and Optional.empty() at the end, once the search has failed. Here is one to put back together.

Put the lines in orderFirstLongWord.java
firstLongWord returns the first word with more than five letters, or an empty Optional if there is none. Put the lines in order so that the program prints Optional[giraffe] and then Optional.empty.

Tap a line, then tap where it should go. Or use the arrows.

Getting the value out#

An Optional is only useful once you take the value out, and the methods for that differ in what they do when there is no value. The plainest way asks first.

Asking first: isPresent, isEmpty and get

isPresent() returns true when there is a value, and isEmpty(), added in Java 11, returns true when there is not. get() returns the value, and throws a NoSuchElementException when there is none.

AskFirst.java
1import java.util.List;
2import java.util.Optional;
3
4public class Main {
5 public static void main(String[] args) {
6 Library library = new Library();
7
8 Optional<Book> found = library.findById(2);
9 if (found.isPresent()) {
10 System.out.println("Found " + found.get().title());
11 }
12
13 Optional<Book> missing = library.findById(7);
14 System.out.println(missing.isPresent() + " " + missing.isEmpty());
15 System.out.println(missing.get().title());
16 }
17}
18
19class Library {
20 private final List<Book> books = List.of(
21 new Book(1, "Emma"),
22 new Book(2, "Dune"),
23 new Book(3, "Beloved"));
24
25 Optional<Book> findById(int id) {
26 for (Book book : books) {
27 if (book.id() == id) {
28 return Optional.of(book);
29 }
30 }
31 return Optional.empty();
32 }
33}
34
35record Book(int id, String title) {}
Prints
›Found Dune
›false true
!Exception in thread "main" java.util.NoSuchElementException: No value present
! at Main.main(Main.java:15)
Stops on line 15

NoSuchElementException: No value present

There was nothing left to take.

It prints Found Dune and false true, and then line 15 stops the program with java.util.NoSuchElementException: No value present. Calling get() on an empty Optional crashes just as surely as calling title() on null.

So asking first only moves the old null check: if (found.isPresent()) instead of if (book != null). It works, and you will see it in real code. The methods in the rest of this section are shorter, and they have no get() that you could call without the check.

A stand-in value: orElse

orElse(other) returns the value if there is one, and other if there is not. It suits a missing value that has a sensible stand-in. The library has a second lookup, borrowerOf, which tells you who has a book. Most books are on the shelf, so it returns an Optional<String>: the borrower's name, or empty.

OrElse.java
1import java.util.Map;
2import java.util.Optional;
3
4public class Main {
5 public static void main(String[] args) {
6 Library library = new Library();
7
8 String who = library.borrowerOf(2).orElse("nobody");
9 System.out.println("Book 2 is with " + who);
10
11 who = library.borrowerOf(1).orElse("nobody");
12 System.out.println("Book 1 is with " + who);
13 }
14}
15
16class Library {
17 private final Map<Integer, String> loans = Map.of(2, "Grace", 3, "Alan");
18
19 Optional<String> borrowerOf(int bookId) {
20 return Optional.ofNullable(loans.get(bookId));
21 }
22}
Prints
›Book 2 is with Grace
›Book 1 is with nobody

It prints Book 2 is with Grace and Book 1 is with nobody. Notice that who is a plain String. orElse takes the value out of the box, so the rest of the code never sees an Optional.

A stand-in made only when needed: orElseGet

Sometimes the stand-in costs something to make: reading a file, asking a server, or searching old records. In the next program that work is checkPaperRecords(), which prints a line so that you can see when it runs. Decide what the program prints before you run it.

Predict the outputPaperRecords.java
1import java.util.Map;
2import java.util.Optional;
3
4public class Main {
5 public static void main(String[] args) {
6 Library library = new Library();
7
8 System.out.println(library.borrowerOf(2).orElse(checkPaperRecords()));
9 System.out.println(library.borrowerOf(2).orElseGet(() -> checkPaperRecords()));
10 System.out.println(library.borrowerOf(1).orElseGet(() -> checkPaperRecords()));
11 }
12
13 static String checkPaperRecords() {
14 System.out.println("(checking the paper records)");
15 return "unknown";
16 }
17}
18
19class Library {
20 private final Map<Integer, String> loans = Map.of(2, "Grace", 3, "Alan");
21
22 Optional<String> borrowerOf(int bookId) {
23 return Optional.ofNullable(loans.get(bookId));
24 }
25}

Which output does this program produce?

It is the second one. On line 8, checkPaperRecords() runs even though book 2 has a borrower. Java works out every argument of a method before the method starts, so it calls checkPaperRecords() to get the argument for orElse, and only then does orElse look in the box, find Grace and throw "unknown" away. orElse receives a finished value, so it has no way to skip making it.

orElseGet takes a Supplier instead. Supplier<T>, from java.util.function, is a functional interface whose one method, get(), takes nothing and returns a T. A lambda that takes nothing starts with empty parentheses: () -> checkPaperRecords(). orElseGet calls the lambda only when the box is empty, which is why the records are checked for book 1 on line 10 and not for book 2 on line 9.

Key idea

orElse always works out its argument, even when the value is there. When the stand-in takes real work to make, use orElseGet with a lambda, which runs only for an empty Optional.

orElse is fine for a fixed value
For a value that already exists, such as "nobody", 0 or an empty list, orElse is simpler and costs nothing extra. Switch to orElseGet when making the stand-in does work or has a side effect, such as printing or reading a file.

No value is a mistake: orElseThrow

Sometimes a missing value means something has gone wrong. An id that came from your own list of books should always be found. orElseThrow(), added in Java 10, returns the value or throws a NoSuchElementException with the message No value present. It does exactly what get() does, but its name tells the reader that it can throw, and the documentation of get() names orElseThrow() as the method to prefer.

To throw an exception of your own, with a message that helps, pass orElseThrow a lambda that makes the exception. The lambda runs only when the box is empty. From here on, findById is a stream pipeline, because findFirst() already returns the Optional the method needs.

OrElseThrow.java
1import java.util.List;
2import java.util.NoSuchElementException;
3import java.util.Optional;
4
5public class Main {
6 public static void main(String[] args) {
7 Library library = new Library();
8
9 Book dune = library.findById(2).orElseThrow();
10 System.out.println("Reading " + dune.title());
11
12 try {
13 Book lost = library.findById(7).orElseThrow();
14 System.out.println("Reading " + lost.title());
15 } catch (NoSuchElementException e) {
16 System.out.println("Caught: " + e.getMessage());
17 }
18
19 Book book = library.findById(7)
20 .orElseThrow(() -> new IllegalArgumentException("No book has id 7"));
21 System.out.println("Reading " + book.title());
22 }
23}
24
25class Library {
26 private final List<Book> books = List.of(
27 new Book(1, "Emma"),
28 new Book(2, "Dune"),
29 new Book(3, "Beloved"));
30
31 Optional<Book> findById(int id) {
32 return books.stream()
33 .filter(book -> book.id() == id)
34 .findFirst();
35 }
36}
37
38record Book(int id, String title) {}
Prints
›Reading Dune
›Caught: No value present
!Exception in thread "main" java.lang.IllegalArgumentException: No book has id 7
! at Main.lambda(Main.java:20)
! at Main.main(Main.java:19)
Stops on line 19

IllegalArgumentException: No book has id 7

A method was given a value it does not accept.

It prints Reading Dune, and then Caught: No value present from the catch block. The last lookup throws the exception that the lambda made, and the program stops with java.lang.IllegalArgumentException: No book has id 7. That message names the missing id, at the place where the lookup failed, which is much easier to trace than a NullPointerException several methods later.

get() and orElseThrow() both throw on an empty Optional
Call them only when you are certain there is a value, or when an empty Optional really is a bug that should stop the program. In every other case, use orElse, orElseGet or the methods in the next section. When you do want the exception, write orElseThrow(), so that a reader can see it from the name.
MethodWith a valueWhen empty
isPresent()truefalse
isEmpty()falsetrue
get()the valuethrows NoSuchElementException
orElse(other)the valueother
orElseGet(supplier)the valuewhatever the supplier returns
orElseThrow()the valuethrows NoSuchElementException
orElseThrow(supplier)the valuethrows the exception the supplier makes
isEmpty() arrived in Java 11 and orElseThrow() with no arguments in Java 10. The others have been there since Optional itself arrived, in Java 8.
Quick check
There is no book with id 7. Which line runs without throwing an exception?

Working inside the box: map, filter and flatMap#

Often what you need is something made from the value, such as the title of the book in the box. map(function) makes it without opening the box. If there is a value, map passes it to the function and puts the result in a new Optional. If the box is empty, map never calls the function and hands back an empty Optional.

MapTitle.java
1import java.util.List;
2import java.util.Optional;
3
4public class Main {
5 public static void main(String[] args) {
6 Library library = new Library();
7
8 Optional<String> title = library.findById(2).map(Book::title);
9 Optional<String> noTitle = library.findById(7).map(Book::title);
10 System.out.println(title + " " + noTitle);
11
12 String label = library.findById(7)
13 .map(book -> book.title().toUpperCase())
14 .orElse("NO SUCH BOOK");
15 System.out.println(label);
16 }
17}
18
19class Library {
20 private final List<Book> books = List.of(
21 new Book(1, "Emma"),
22 new Book(2, "Dune"),
23 new Book(3, "Beloved"));
24
25 Optional<Book> findById(int id) {
26 return books.stream()
27 .filter(book -> book.id() == id)
28 .findFirst();
29 }
30}
31
32record Book(int id, String title) {}
Prints
›Optional[Dune] Optional.empty
›NO SUCH BOOK

It prints Optional[Dune] Optional.empty, and then NO SUCH BOOK. Step through lines 12 to 14 and watch the narration: findById(7) returns an empty Optional, and the lambda inside map never runs. The chain reads like a sentence, with no if for the empty case: find book 7, turn it into its title in capitals, or else use NO SUCH BOOK.

filter: keep the value only if it passes a test

filter(test) keeps the value when the test returns true for it, and gives back an empty Optional when the test returns false. An empty Optional has nothing to test, so it stays empty.

One case catches people out. A shopper types three spaces into the box for a discount code. Predict what the program prints.

Predict the outputBlankCode.java
1import java.util.Optional;
2
3public class Main {
4 public static void main(String[] args) {
5 Optional<String> code = Optional.of(" ");
6 Optional<String> trimmed = code.map(c -> c.trim());
7 Optional<String> real = trimmed.filter(c -> !c.isEmpty());
8
9 System.out.println(code.isPresent());
10 System.out.println(trimmed.isPresent());
11 System.out.println(real.isPresent());
12 }
13}

Which output does this program produce?

It prints true, true and false. trim() turns " " into "", the empty string, and map puts that in a new box. A box that holds the empty string is present, because the empty string is still a value. Only filter, whose test fails for "", gives back an empty Optional. Printed, trimmed would show Optional[] and real would show Optional.empty.

Watch one value travel down a chain

The lab runs a greetingmethod for three users. It looks up the user's nickname, trims the spaces off it, drops it if nothing is left, and builds a greeting, with "Hello, guest" as the stand-in. Pick a user and press Next step to run one step at a time. Watch which lambdas run, and where an empty box makes the later steps skip theirs.

One box down the chainOptional<String>

Ada's nickname has spaces around it.

  1. return findNickname(user)
  2. .map(name -> name.trim())
  3. .filter(name -> !name.isEmpty())
  4. .map(name -> "Hello, " + name)
  5. .orElse("Hello, guest");
lambdas run
0 of 3
lambdas skipped
0

Nothing has run yet. Press Next step to call greeting("ada"), which starts with findNickname.

Here is the same method in a program. Step through it and follow the narration: for cleo, none of the three lambdas runs.

Greeting.java
1import java.util.Map;
2import java.util.Optional;
3
4public class Main {
5 static Map<String, String> nicknames = Map.of("ada", " Ada ", "ben", " ");
6
7 static Optional<String> findNickname(String user) {
8 return Optional.ofNullable(nicknames.get(user));
9 }
10
11 static String greeting(String user) {
12 return findNickname(user)
13 .map(name -> name.trim())
14 .filter(name -> !name.isEmpty())
15 .map(name -> "Hello, " + name)
16 .orElse("Hello, guest");
17 }
18
19 public static void main(String[] args) {
20 System.out.println(greeting("ada"));
21 System.out.println(greeting("ben"));
22 System.out.println(greeting("cleo"));
23 }
24}
Prints
›Hello, Ada
›Hello, guest
›Hello, guest

It prints Hello, Ada and then Hello, guesttwice. Ben and Cleo get the same greeting for different reasons: Ben's box was emptied by filter, while Cleo's was empty from the start.

The same names as on a stream
map and filter work like the stream operations with the same names, on a stream that holds at most one element. Optional and streams both arrived in Java 8, and you have already seen stream methods such as findFirst() hand you an Optional.
Key idea

Once an Optional is empty, every map and filter after it is skipped. The empty box travels to the end of the chain, where orElse or one of its relatives decides what to do.

flatMap: when the function returns an Optional too

Now ask a question with two lookups in it: who has the book called Dune? First find the book by its title, which may find nothing. Then look up its borrower, which may also find nothing. Both lookups return an Optional. Here is what happens when you join them with map, and then with flatMap.

WhoHasIt.java
1import java.util.List;
2import java.util.Map;
3import java.util.Optional;
4
5public class Main {
6 public static void main(String[] args) {
7 Library library = new Library();
8
9 Optional<Optional<String>> nested = library.findByTitle("Dune")
10 .map(book -> library.borrowerOf(book.id()));
11 System.out.println(nested);
12
13 for (String title : List.of("Dune", "Emma", "Ulysses")) {
14 Optional<String> who = library.findByTitle(title)
15 .flatMap(book -> library.borrowerOf(book.id()));
16 System.out.println(title + ": " + who);
17 }
18 }
19}
20
21class Library {
22 private final List<Book> books = List.of(
23 new Book(1, "Emma"),
24 new Book(2, "Dune"),
25 new Book(3, "Beloved"));
26 private final Map<Integer, String> loans = Map.of(2, "Grace", 3, "Alan");
27
28 Optional<Book> findByTitle(String title) {
29 return books.stream()
30 .filter(book -> book.title().equals(title))
31 .findFirst();
32 }
33
34 Optional<String> borrowerOf(int bookId) {
35 return Optional.ofNullable(loans.get(bookId));
36 }
37}
38
39record Book(int id, String title) {}
Prints
›Optional[Optional[Grace]]
›Dune: Optional[Grace]
›Emma: Optional.empty
›Ulysses: Optional.empty

The first line prints Optional[Optional[Grace]], a box inside a box. The function passed to map already returns an Optional<String>, and map wraps whatever its function returns in another Optional, so the result is an Optional<Optional<String>>. To get Grace out of that you would have to open two boxes.

flatMap is made for this case. It expects a function that returns an Optional, and hands that Optional back as it is, without wrapping it again. The loop prints Dune: Optional[Grace], Emma: Optional.empty and Ulysses: Optional.empty. Emma is in the catalog but nobody has borrowed it; Ulysses is not in the catalog at all. Both come out empty, because flatMap gives back an empty Optional as soon as either lookup finds nothing. When your program has to tell those two cases apart, do the lookups one at a time.

map(f)
flatMap(f)
For an f that returns a plain value, such as a String.
For an f that returns an Optional, such as an Optional<String>.
Wraps f's result in a new Optional.
Returns f's Optional as it is.
If f returns null, the result is an empty Optional.
f must never return null: flatMap throws a NullPointerException if it does.
Quick check
findById returns an Optional<Book> and borrowerOf returns an Optional<String>. What is the type of library.findById(2).map(book -> library.borrowerOf(book.id()))?
ExerciseFirstLongSong.java
Finish firstLongerThan so that it returns the first song in the list that is longer than seconds, or an empty Optional if no song is. main is already written and should print Intro, Night Drive and none.

Acting on the value: ifPresent, or and stream#

ifPresent and ifPresentOrElse

Sometimes there is nothing to return, only something to do with the value: print it, add it to a list, send it somewhere. ifPresent(action) runs the action with the value if there is one, and does nothing for an empty Optional. ifPresentOrElse(action, emptyAction), from Java 9, takes a second action for the empty case.

Reserve.java
1import java.util.List;
2import java.util.Optional;
3
4public class Main {
5 public static void main(String[] args) {
6 Library library = new Library();
7
8 library.findById(2)
9 .ifPresent(book -> System.out.println("Reserved " + book.title()));
10 library.findById(7)
11 .ifPresent(book -> System.out.println("Reserved " + book.title()));
12
13 for (int id : List.of(3, 9)) {
14 library.findById(id).ifPresentOrElse(
15 book -> System.out.println("Reserved " + book.title()),
16 () -> System.out.println("No book has id " + id));
17 }
18 }
19}
20
21class Library {
22 private final List<Book> books = List.of(
23 new Book(1, "Emma"),
24 new Book(2, "Dune"),
25 new Book(3, "Beloved"));
26
27 Optional<Book> findById(int id) {
28 return books.stream()
29 .filter(book -> book.id() == id)
30 .findFirst();
31 }
32}
33
34record Book(int id, String title) {}
Prints
›Reserved Dune
›Reserved Beloved
›No book has id 9

It prints Reserved Dune, then Reserved Beloved and No book has id 9. The lookup of book 7 on lines 10 and 11 prints nothing at all. The second argument of ifPresentOrElse is a Runnable, a functional interface whose one method, run(), takes nothing and returns nothing, so its lambda starts with () as well. You will meet Runnable again in introduction to threads.

or: a second place to look

or(supplier), also from Java 9, keeps an Optional that has a value. For an empty one it calls the supplier, which returns another Optional to use instead. Unlike orElse, it gives you an Optional back, so you can try a third place or carry on with map. Here a town library falls back on the school library's catalog.

TwoLibraries.java
1import java.util.List;
2import java.util.Optional;
3
4public class Main {
5 public static void main(String[] args) {
6 Library town = new Library(List.of(
7 new Book(1, "Emma"),
8 new Book(2, "Dune")));
9 Library school = new Library(List.of(
10 new Book(3, "Beloved"),
11 new Book(4, "Kindred")));
12
13 for (int id : List.of(2, 4, 9)) {
14 Optional<Book> found = town.findById(id)
15 .or(() -> school.findById(id));
16 System.out.println(id + ": " + found);
17 }
18 }
19}
20
21class Library {
22 private final List<Book> books;
23
24 Library(List<Book> books) {
25 this.books = books;
26 }
27
28 Optional<Book> findById(int id) {
29 return books.stream()
30 .filter(book -> book.id() == id)
31 .findFirst();
32 }
33}
34
35record Book(int id, String title) {}
Prints
›2: Optional[Book[id=2, title=Dune]]
›4: Optional[Book[id=4, title=Kindred]]
›9: Optional.empty

It prints 2: Optional[Book[id=2, title=Dune]], 4: Optional[Book[id=4, title=Kindred]] and 9: Optional.empty. Book 2 is in the town library, so the school's catalog is never searched. Book 4 comes from the fallback, and book 9 is in neither.

stream(): zero elements or one

stream(), from Java 9 as well, turns an Optional into a stream with one element, or with none when the Optional is empty. It is handy when you have a stream of Optionals: together with the flatMap from Collectors and stream recipes, it drops the empty ones and keeps the values.

OnTheShelf.java
1import java.util.List;
2import java.util.Optional;
3
4public class Main {
5 public static void main(String[] args) {
6 Library library = new Library();
7
8 System.out.println(library.findById(2).stream().toList());
9 System.out.println(library.findById(7).stream().toList());
10
11 List<Integer> wanted = List.of(3, 7, 1, 9);
12 List<Book> onShelf = wanted.stream()
13 .map(id -> library.findById(id))
14 .flatMap(Optional::stream)
15 .toList();
16 System.out.println(onShelf);
17 }
18}
19
20class Library {
21 private final List<Book> books = List.of(
22 new Book(1, "Emma"),
23 new Book(2, "Dune"),
24 new Book(3, "Beloved"));
25
26 Optional<Book> findById(int id) {
27 return books.stream()
28 .filter(book -> book.id() == id)
29 .findFirst();
30 }
31}
32
33record Book(int id, String title) {}
Prints
›[Book[id=2, title=Dune]]
›[]
›[Book[id=3, title=Beloved], Book[id=1, title=Emma]]

The first two lines print [Book[id=2, title=Dune]] and []. In the pipeline, map turns each id into an Optional<Book>, and flatMap(Optional::stream) replaces each one with its stream of zero books or one. The ids with no book drop out, and the last line is [Book[id=3, title=Beloved], Book[id=1, title=Emma]], in the order the ids were asked for.

Optionals the library hands you#

You will write methods that return an Optional, and you will call even more of them. Every stream operation that may have no answer returns one.

MethodCalled onReturns
findFirst(), findAny()Stream<T>Optional<T>
max(comparator), min(comparator)Stream<T>Optional<T>
reduce(operator), with no starting valueStream<T>Optional<T>
max(), min()IntStreamOptionalInt
average()IntStreamOptionalDouble
Each one is empty when the stream has no elements. On a LongStream, max() and min() return an OptionalLong; on a DoubleStream they return an OptionalDouble, and so does every average().

The longest word in a list, the method with no good answer at the start of this lesson, takes one line with max. It compares the elements with a Comparator, here one that compares words by length as in Comparable, Comparator and sorting, and returns the biggest in an Optional.

LibraryOptionals.java
1import java.util.Arrays;
2import java.util.Comparator;
3import java.util.List;
4import java.util.Optional;
5import java.util.OptionalDouble;
6import java.util.OptionalInt;
7
8public class Main {
9 public static void main(String[] args) {
10 Comparator<String> byLength = Comparator.comparingInt(String::length);
11 List<String> words = List.of("pear", "banana", "fig");
12 List<String> noWords = List.of();
13
14 Optional<String> longest = words.stream().max(byLength);
15 Optional<String> shortest = words.stream().min(byLength);
16 System.out.println(longest + " " + shortest);
17 System.out.println(noWords.stream().max(byLength));
18
19 int[] scores = {72, 95, 43};
20 OptionalInt top = Arrays.stream(scores).max();
21 OptionalDouble mean = Arrays.stream(scores).average();
22 System.out.println(top + " " + mean);
23 System.out.println(top.getAsInt() + " " + mean.getAsDouble());
24
25 int[] noScores = {};
26 OptionalInt noTop = Arrays.stream(noScores).max();
27 OptionalDouble noMean = Arrays.stream(noScores).average();
28 System.out.println(noTop + " " + noMean);
29 System.out.println(noMean.orElse(0));
30 System.out.println(noTop.getAsInt());
31 }
32}
Prints
›Optional[banana] Optional[fig]
›Optional.empty
›OptionalInt[95] OptionalDouble[70.0]
›95 70.0
›OptionalInt.empty OptionalDouble.empty
›0.0
!Exception in thread "main" java.util.NoSuchElementException: No value present
! at Main.main(Main.java:30)
Stops on line 30

NoSuchElementException: No value present

There was nothing left to take.

It prints Optional[banana] Optional[fig], then Optional.empty for the list with no words. For the scores it prints OptionalInt[95] OptionalDouble[70.0] and 95 70.0. For the empty array it prints OptionalInt.empty OptionalDouble.empty and 0.0, and the last line throws java.util.NoSuchElementException: No value present.

OptionalInt, OptionalLong and OptionalDouble

The number streams return their own kinds of Optional, which hold a plain int, long or double without boxing it. The method that takes the value out has the type in its name: getAsInt(), getAsLong() and getAsDouble(). Like get(), each one throws a NoSuchElementException when there is no value. They also have isPresent, isEmpty, orElse, orElseGet, orElseThrow, ifPresent, ifPresentOrElse and stream. They have no map, filter or flatMap. They have no ofNullable either, because a plain int can never be null.

ExerciseTopScore.java
Finish printTop so that it prints Top score: 95 for the first array and No scores yet for the empty one. Use Arrays.stream(scores).max(), which returns an OptionalInt, and ifPresentOrElse.

Using Optional well#

The Java documentation says that Optionalis meant mainly as a method return type, for when there is a clear need to say “no result” and null would be likely to cause errors. These habits follow from that:

  • Return an Optional from a method that may find nothing, such as a lookup or a search. When a missing value can only mean that the caller made a mistake, throwing an exception straight away is clearer.
  • Never return null from a method that returns an Optional, and never set an Optional variable to null. The documentation says such a variable should always point at an Optional object. An empty Optionalis how you say “nothing”.
  • Never call get() without a check. Prefer orElse, orElseGet, map and ifPresent, and when an empty Optional should stop the program, write orElseThrow(), whose name says so.
  • Use Optional.of only for values that cannot be null, because it throws on null. Use ofNullable for values that might be.
  • Do not wrap a value only to test it. Optional.ofNullable(name).isPresent() is a longer way to write name != null.
  • Compare two Optionals with equals, never with ==. The documentation does not promise that every empty Optional is the same object.
  • Keep Optional out of fields, method parameters and collections.

Fields, parameters and collections

The Optional tutorial on dev.java lists fields, method parameters, collections and maps as places where an Optional does not belong. Each one has a simpler alternative.

Instead of
Write
A field: Optional<String> nickname;
A field String nickname; that may be null inside the class, and a method Optional<String> nickname() that wraps it with ofNullable for callers.
A parameter, as in greet(Optional<String> name)
Two methods, greet() and greet(String name). A caller could still pass null for an Optional parameter, which leaves three cases to handle instead of two.
A List<Optional<Book>>
A List<Book> with the missing books left out.
An Optional<List<Book>>, or null, for “no results”
An empty List<Book>. An empty list already says “none”.
Quick check
Which of these uses Optional the way it was designed to be used?
Key idea

Use Optional as the return type of a method that may find nothing, and almost nowhere else. An Optional itself should never be null.

Try it

Answer each one in your head or on paper first, then open the answer.

1
What does each line of this program print? Explain the second line.
2
A program sets a String label with an if and an else: when library.findById(id) has a book, label is its title in capitals, and otherwise it is MISSING. The if calls found.get() after checking found.isPresent(). Write the same thing as one chain with map and orElse, then open the program to compare the two.
3
A program reads its theme with settings.find("theme").orElse(loadDefaultTheme()), where find returns an Optional<String> and loadDefaultTheme() reads a file. Almost every user has set a theme, yet the file is read every time. Why, and how do you fix it?
4
This program prints true and then stops with a NullPointerException on line 9. Find the bug and fix it.
5
This program uses max, map and orElse to find the name of the student with the highest score, with nobody as the stand-in for an empty list. What does it print?
6
Which method fits each job? (a) A missing value has a fixed stand-in. (b) A missing value means there is a bug. (c) You only want to print the value, if there is one. (d) If the first lookup finds nothing, you want to try a second lookup that also returns an Optional.

Practice

Work through these on your own. Each one builds on this page.

1
Write static Optional<Integer> parseAge(String text). It returns the age for text such as "42", and an empty Optional when the text is not a whole number or the age is below 0 or above 150. Print parseAge("42"), parseAge("forty") and parseAge("200").
2
In wrapper classes, a Player kept its best score in an Integer field that stayed null until the first game. Keep that field, and add a method Optional<Integer> best() that wraps it. Then print player.best().map(s -> "Best: " + s).orElse("No games yet") before the first game and after a few games.
3
Make two maps from a word to its meaning, one of everyday words and one of slang. Write Optional<String> define(String word), which looks in the first map and, if the word is not there, in the second. Print the meaning of a word from each map and of a word that is in neither, with orElse("unknown word").
4
The linear search in searching and sorting returns -1 when it does not find the target. Rewrite it to return an OptionalInt, using OptionalInt.of(i) and OptionalInt.empty(), and change the caller to print found at index 3 or not found. In two sentences, say what the new return type tells a caller that -1 did not.

Further reading#

Spotted a mistake, or a part that did not make sense? Report it by email