Optional Best Practices
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
| Do | Don't |
|---|---|
Return Optional<T> from methods that may find nothing | Use Optional as a field type |
Use orElse, orElseGet, orElseThrow, map, ifPresent | Call get() without being sure a value exists |
Return Optional.empty() when there is no result | Return null from a method declared to return Optional |
| Return an empty list for "no elements" | Return Optional<List<T>> |
Use OptionalInt, OptionalDouble for primitives | Use Optional<Integer> in hot code |
Chain with map and flatMap | Write 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.
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"));
}
}Bad style: NO EMAIL
Good style: NO EMAIL
Navika: NAVIKA@MAIL.COMExplanation: 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>>:
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"));
}
}Pune
unknown
unknownExplanation: 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
- Easy: Refactor a method that returns
nullwhen a product isn't found so that it returns anOptional<Product>, and update one caller to useorElseThrow. - 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, usingflatMap(Optional::stream).
Key Takeaways
Was this tutorial helpful?