Optional<T> makes "this might be absent" explicit in the type, instead
of hiding it behind a null the caller forgets to check.
Java
import java.util.Optional;
public Optional<Book> findById(int id) {
Book b = database.get(id);
return Optional.ofNullable(b);
}Creating
Java
Optional<String> a = Optional.of("value"); // throws if null
Optional<String> b = Optional.ofNullable(maybe); // null-safe
Optional<String> c = Optional.empty();Consuming
Java
Optional<Book> found = findById(5);
if (found.isPresent()) {
System.out.println(found.get().getTitle());
}
found.ifPresent(book -> System.out.println(book.getTitle()));
found.ifPresentOrElse(
book -> System.out.println(book.getTitle()),
() -> System.out.println("Not found")
);
Book book = found.orElse(new Book("Unknown", 0));
Book book2 = found.orElseGet(() -> loadDefault()); // lazy — only runs if empty
Book book3 = found.orElseThrow(() -> new NotFoundException("No book " + id));Prefer orElseGet over orElse when the fallback is expensive:
orElse's argument is evaluated always, even when the Optional has a
value.
Chaining
Java
String city = findUser(id)
.map(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");Each map is skipped if the Optional is empty — no nested null checks.
Rules of use
Do return Optional from methods that may find nothing.
Don't:
- Use it for fields or method parameters — it isn't serializable and adds noise.
- Call
.get()without checking; that just moves the NPE. - Return
Optional<List<T>>— return an empty list instead. - Use it for collections at all: an empty collection already means "nothing".
Java
// good
Optional<User> findByEmail(String email);
// bad
void save(Optional<User> user); // just accept User
List<Book> getBooks(); // return empty list, never Optional