Optional Best Practices

Naveen AnamaneniUpdated · 4 min read

Optional was designed for one main job: to be the return type of a method that may have no result. Used that way, it makes code safer and clearer. Used everywhere, as a field type, a parameter type or a replacement for every null check, it makes code longer and slower without adding safety. In this lesson, you will learn the rules for using Optional well, with examples of good and bad use.

Rules for Using Optional

DoDon't
Return Optional<T> from methods that may find nothingUse Optional as a field type
Use orElse, orElseGet, orElseThrow, map, ifPresentCall get() without being sure a value exists
Return Optional.empty() when there is no resultReturn null from a method declared to return Optional
Return an empty list for "no elements"Return Optional<List<T>>
Use OptionalInt, OptionalDouble for primitivesUse Optional<Integer> in hot code
Chain with map and flatMapWrite if (opt.isPresent()) ... opt.get()
Accept plain parameters (use overloading for "optional" arguments)Take Optional as a method parameter

Example: Good and Bad Style

Let's see an example comparing the two styles for the same task.

Styles.java▶ Try it
import java.util.Map;
import java.util.Optional;
 
public class Styles {
    static final Map<String, String> EMAILS =
        Map.of("Navika", "navika@mail.com", "Srihitha", "");
 
    static Optional<String> email(String name) {
        return Optional.ofNullable(EMAILS.get(name))
                       .filter(e -> !e.isBlank());
    }
 
    public static void main(String[] args) {
        Optional<String> e = email("Srihitha");
        String bad;
        if (e.isPresent()) {
            bad = e.get().toUpperCase();
        } else {
            bad = "NO EMAIL";
        }
        System.out.println("Bad style:  " + bad);
 
        String good = email("Srihitha").map(String::toUpperCase)
                                       .orElse("NO EMAIL");
        System.out.println("Good style: " + good);
        System.out.println("Navika:     "
            + email("Navika").map(String::toUpperCase).orElse("NO EMAIL"));
    }
}
Output
Bad style:  NO EMAIL
Good style: NO EMAIL
Navika:     NAVIKA@MAIL.COM

Explanation: email returns an empty Optional both when there is no entry and when the email is blank. The "bad style" version is just a null check with extra steps. The "good style" version says what should happen in one expression: transform the email if there is one, otherwise use a default.

Example: Chaining with flatMap

When a step itself returns an Optional, use flatMap so the result doesn't become Optional<Optional<T>>:

Chain.java▶ Try it
import java.util.Map;
import java.util.Optional;
 
public class Chain {
    record Address(String city) { }
    record Student(String name, Optional<Address> address) { }
 
    static final Map<String, Student> DB = Map.of(
        "Navika", new Student("Navika", Optional.of(new Address("Pune"))),
        "Advitha", new Student("Advitha", Optional.empty()));
 
    static Optional<Student> find(String name) {
        return Optional.ofNullable(DB.get(name));
    }
 
    static String cityOf(String name) {
        return find(name)
            .flatMap(Student::address)
            .map(Address::city)
            .orElse("unknown");
    }
 
    public static void main(String[] args) {
        System.out.println(cityOf("Navika"));
        System.out.println(cityOf("Advitha"));
        System.out.println(cityOf("Srihitha"));
    }
}
Output
Pune
unknown
unknown

Explanation: find returns an Optional<Student>, and Student::address returns an Optional<Address>, so flatMap is needed to avoid nesting. The chain returns "unknown" whether the student is missing (Srihitha) or has no address (Advitha). (The record field is Optional here only to demonstrate flatMap; in real code, prefer a method Optional<Address> address() that wraps a plain field.)

Common Mistakes

Optional<String> x = null; This defeats the purpose; return Optional.empty().

Using Optional for collections. Return an empty collection for "nothing".

Calling orElse(expensiveCall()). The call always runs; use orElseGet.

Using Optional.isPresent() then get(). Use ifPresent, map or orElse.

Practice

  1. Easy: Refactor a method that returns null when a product isn't found so that it returns an Optional<Product>, and update one caller to use orElseThrow.
  2. Harder: Given a list of user IDs and a method Optional<User> load(int id), collect the names of the users that exist into a list, using flatMap(Optional::stream).

Quick check: Optional best practices

4 questions
1Where is Optional meant to be used?
2What should a method declared to return Optional return when there's no result?
3What should a method return when there are no matching elements?
4Which method avoids Optional<Optional>?

Key Takeaways

Was this tutorial helpful?

Lesson 326 of 482