Логотип Workflow

Article

Stream Api And Optional

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.

Stream pipeline

1. Stream API Mental Model

A Stream is not a container. It is a computation pipeline over a source.

Core structure:

  1. source,
  2. intermediate operations (filter, map, flatMap, ...),
  3. 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

SituationBetter fit
Linear transformationStream
Branch-heavy stateful logicLoop
Aggregation/grouping flowStream
Step-by-step debuggingLoop

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, orElseThrow
  • map, flatMap, filter
  • ifPresent, 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

  1. Very long stream chains with dense business logic.
  2. Hidden side effects inside map/filter.
  3. Calling Optional.get() without a safety guarantee.
  4. 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.

Please login to pass quizzes.

Quiz

Check what you learned

Practice

Interactive practice

Complete tasks and check your answer instantly.