Topic 10. Stream API and Optional
Stream API and Optional make Java data-processing code more predictable and readable. Streams model transformations as pipelines, while Optional makes “value may be absent” explicit. For beginners, this combination reduces null-related bugs and deeply nested imperative code.
1. Stream API Mental Model
A Stream is not a container. It is a computation pipeline over a source.
Core structure:
- source,
- intermediate operations (
filter,map,flatMap, ...), - terminal operation (
toList,collect,count,findFirst, ...).
Example:
List<String> names = List.of("ann", "bob", "alex", "anna");
List<String> result = names.stream()
.filter(n -> n.length() > 3)
.map(String::toUpperCase)
.sorted()
.toList();
2. Stream Laziness
Intermediate operations are lazy. Until terminal operation is called, pipeline is not executed.
Stream<String> s = names.stream().filter(n -> n.startsWith("a"));
// not executed yet
long count = s.count(); // triggers execution
This enables optimization and avoids unnecessary processing.
3. Common Operations with Practical Examples
map
List<Integer> lengths = names.stream()
.map(String::length)
.toList();
filter
List<String> shortNames = names.stream()
.filter(n -> n.length() <= 3)
.toList();
flatMap
List<List<String>> tags = List.of(
List.of("java", "backend"),
List.of("backend", "spring")
);
List<String> uniqueTags = tags.stream()
.flatMap(List::stream)
.distinct()
.sorted()
.toList();
collect with grouping
Map<Integer, List<String>> byLength = names.stream()
.collect(java.util.stream.Collectors.groupingBy(String::length));
reduce
List<Integer> prices = List.of(100, 250, 80, 70);
int total = prices.stream().reduce(0, Integer::sum);
4. Stream vs Loop: Practical Choice
| Situation | Better fit |
|---|---|
| Linear transformation | Stream |
| Branch-heavy stateful logic | Loop |
| Aggregation/grouping flow | Stream |
| Step-by-step debugging | Loop |
Use what your team can read and maintain faster.
5. Optional: Explicit Absence Contract
Optional<T> communicates that value may be missing.
Optional<String> email = findEmailByUserId(10L);
String value = email.orElse("[email protected]");
Unlike hidden null, Optional forces explicit handling at call sites.
6. Core Optional Operations
orElse,orElseGet,orElseThrowmap,flatMap,filterifPresent,ifPresentOrElse
Null-safe chain example:
Optional<User> user = findUser(id);
String city = user
.map(User::getAddress)
.map(Address::getCity)
.filter(c -> !c.isBlank())
.orElse("Unknown");
7. orElse vs orElseGet
String a = opt.orElse(expensiveFallback());
String b = opt.orElseGet(() -> expensiveFallback());
orElse evaluates fallback eagerly. orElseGet evaluates lazily.
8. Frequent Beginner Mistakes
- Very long stream chains with dense business logic.
- Hidden side effects inside
map/filter. - Calling
Optional.get()without a safety guarantee. - Using
parallelStream()without measurement.
9. Better vs Worse Examples
Worse:
String city = user.getAddress().getCity(); // NullPointerException risk
Better:
String city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity)
.orElse("Unknown");
Worse:
list.stream().map(x -> { log(x); return transform(x); }).toList();
Prefer separating transformation from side-effect logic.
10. Key Takeaway
Streams are for declarative data pipelines. Optional is for explicit absence semantics. Together they improve clarity and safety when used with discipline: short readable chains, minimal side effects, and pragmatic stream-vs-loop choices.