Optional in Java
Optional makes absence part of the type, so a caller cannot forget that a value might not be there.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
The problem
public Note findById(long id) {
return null; // nothing in the signature warns the caller
}
Note note = repository.findById(42);
System.out.println(note.title()); // NullPointerException, eventuallyA method returning null gives no hint that it might. Optional<T> puts the possibility into the type, so the compiler forces the caller to think about it.
public Optional<Note> findById(long id) {
return Optional.empty();
}
String title = repository.findById(42)
.map(Note::title)
.orElse("not found");Creating an Optional
Optional<String> present = Optional.of("java"); // throws if the value is null
Optional<String> maybe = Optional.ofNullable(lookup()); // null becomes empty
Optional<String> empty = Optional.empty();Use of when null would be a bug, and ofNullable when it is a legitimate outcome.
Getting a value out
Optional<Note> found = repository.findById(42);
String a = found.map(Note::title).orElse("unknown"); // a default
String b = found.map(Note::title).orElseGet(this::defaultTitle); // computed lazily
Note c = found.orElseThrow(); // NoSuchElementException
Note d = found.orElseThrow(() -> new NoteNotFoundException(42));
found.ifPresent(note -> System.out.println(note.title()));
found.ifPresentOrElse(
note -> System.out.println(note.title()),
() -> System.out.println("nothing found"));orElseevaluates its argument every time, even when a value is present.orElseGettakes a supplier and calls it only when empty. When the default is expensive, or has a side effect, that difference matters.
// The database is queried even when the optional has a value
found.orElse(loadFromDatabase());
// Queried only when empty
found.orElseGet(() -> loadFromDatabase());Transforming without unwrapping
Optional<String> upper = repository.findById(42)
.filter(Note::isPublished)
.map(Note::title)
.map(String::toUpperCase);
Optional<String> author = repository.findById(42)
.flatMap(Note::authorName); // when the method itself returns Optional| Method | Purpose |
|---|---|
map | Transform the value if present |
flatMap | Transform when the function returns an Optional |
filter | Keep the value only if it matches |
or | Supply an alternative Optional |
stream | Zero or one element, for use in a pipeline |
isPresent, isEmpty | Test, but prefer the methods above |
Optional<Note> note = repository.findById(id)
.or(() -> archive.findById(id)); // fall back to a second sourceReplacing null checks
// Nested null checks
String city = null;
if (user != null) {
Address address = user.getAddress();
if (address != null) {
city = address.getCity();
}
}
if (city == null) {
city = "unknown";
}
// The same intent, expressed once
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("unknown");With streams
Optional<Note> mostViewed = notes.stream()
.max(Comparator.comparingInt(Note::views));
List<Note> found = ids.stream()
.map(repository::findById) // Stream<Optional<Note>>
.flatMap(Optional::stream) // drops the empties
.toList();Several terminal operations return an Optional, precisely because an empty stream has no result to give.
Where Optional does not belong
| Use it for | Do not use it for |
|---|---|
| A return type that may have no result | Fields of a class |
| Chaining transformations over a maybe value | Method parameters |
| Making absence explicit in an API | Collections; return an empty one instead |
Anything serialised, as it is not Serializable |
// Poor: the caller must wrap every argument
public void save(Optional<String> title) { }
// Better: overload, or accept null with documentation
public void save(String title) { }
public void save() { }
// Poor: an empty list already means "nothing"
public Optional<List<Note>> findAll() { }
// Better
public List<Note> findAll() { return List.of(); }Common mistakes
- Calling
get()without checking. It was renamed in spirit toorElseThrow()for good reason. - Writing
if (opt.isPresent()) { opt.get() }, which is the null check with more typing. - Using
orElsewith an expensive or side effecting expression. - Returning
Optional<Collection>instead of an empty collection. - Using
Optionalas a field or a parameter type. - Returning
nullfrom a method declared to returnOptional, which is the worst of both worlds.
Best practices
- Use it as a return type for a lookup that may find nothing.
- Prefer
map,filterandorElseGetover explicit presence tests. - Use
orElseThrowwith a specific exception when absence is a real error. - Never return
nullfrom a method that returnsOptional. - Keep it out of fields, parameters and serialised types.
Practice
- Rewrite a three level nested null check as a single
Optionalchain. - Explain the difference between
orElse(expensive())andorElseGet(this::expensive)with a print statement. - When would
flatMapbe needed rather thanmap? - Why should a repository return
List.of()rather thanOptional<List>? - Convert a method that returns
nullinto one returningOptional, and update two call sites.
Conclusion
Optional exists to make absence visible in a method signature. Use it for return values, chain with map and filter rather than unwrapping early, and keep it out of fields and parameters.