Back to Stream Creation & Utilities
Q3Question 3 of 18

Stream.ofNullable (Java 9) + Stream.concat: null-safe stream creation and lazy merging

Key Concepts
  • Stream.ofNullable(value) returns a one-element stream if non-null, or an empty stream if null
  • Replaces the verbose value != null ? Stream.of(value) : Stream.empty() ternary pattern
  • Most powerful inside flatMap to safely include nullable values: flatMap(t -> Stream.ofNullable(t.getNote()))
  • Stream.concat(a, b) lazily merges two streams: elements from a first, then b
  • Neither stream is consumed until a terminal operation triggers the concatenated stream
  • For 3+ streams, Stream.of(s1, s2, s3).flatMap(s -> s) is cleaner than nested concat calls
  • concat propagates close() to both input streams for resource-backed streams like Files.lines

Definition

Stream.ofNullable returns a one-element or empty stream from a nullable value, and Stream.concat lazily merges two streams end-to-end.
DIAGRAM
- **ofNullable(value)** – Java 9; returns `Stream.of(value)` if non-null, `Stream.empty()` if null. Eliminates null-check ternaries.
- **Best use** – inside `flatMap` to safely include nullable fields: `flatMap(t -> Stream.ofNullable(t.getNote()))`.
- **concat(a, b)** – lazily concatenates two streams; elements from `a` first, then `b`. Neither consumed until terminal op.
- **Close propagation** – closing the concatenated stream closes both inputs (important for Files.lines).
- **3+ streams** – prefer `Stream.of(s1, s2, s3).flatMap(s -> s)` over nested concat to avoid deep pipeline nesting.

ofNullable is like checking a bank mailbox: if there is a letter (non-null), put it on the conveyor belt. If empty (null), put nothing. No NullPointerException. concat is like connecting two conveyor belts end-to-end: the first belt's items come through, then the second belt's items.

Understanding the Concepts

What is Stream.ofNullable?

Stream.ofNullable(value) (Java 9) returns a one-element stream if value is non-null, or an empty stream if null.

JAVA
package com.bank.service;

import java.util.stream.Stream;

public class AccountService {

    // BEFORE Java 9: verbose null check
    public Stream<String> getSymbolStream(String symbol) {
        return symbol != null ? Stream.of(symbol) : Stream.empty();
    }

    // AFTER Java 9: one-liner
    public Stream<String> getSymbolStreamClean(String symbol) {
        return Stream.ofNullable(symbol);
    }
}

How is ofNullable used in flatMap?

JAVA
import java.util.List;
import java.util.stream.Stream;

// Collect all non-null risk notes from trades
List<String> notes = trades.stream()
    .flatMap(t -> Stream.ofNullable(t.getRiskNote()))
    .toList();
// Null notes become empty streams → skipped. No NullPointerException.

// For nullable collections:
Stream<String> txns = Stream.ofNullable(nullableTransactionList)
    .flatMap(java.util.Collection::stream);
// null list → empty stream. Non-null list → stream of elements.

What is Stream.concat?

JAVA
import java.util.stream.Stream;

// Merge two trade streams from different sources
Stream<Trade> combined = Stream.concat(
    exchangeTrades.stream(),
    internalTrades.stream());
// Lazy: no processing yet. Elements from exchange first, then internal.

List<Trade> all = combined.filter(Trade::isSettled).toList();
// NOW both streams are consumed through the filter.

How do you merge 3+ streams?

JAVA
// WRONG: deeply nested concat
Stream<Trade> nested = Stream.concat(
    Stream.concat(exchangeTrades.stream(), internalTrades.stream()),
    manualTrades.stream());
// Deep nesting, hard to read for many sources

// CORRECT: flatMap identity for many streams
Stream<Trade> flat = Stream.of(
    exchangeTrades.stream(),
    internalTrades.stream(),
    manualTrades.stream()
).flatMap(s -> s);
// Clean for any number of sources

What about resource-backed streams?

JAVA
import java.nio.file.Files;
import java.nio.file.Path;

// concat propagates close() to both streams
try (Stream<String> combined = Stream.concat(
        Files.lines(Path.of("accounts_q1.csv")),
        Files.lines(Path.of("accounts_q2.csv")))) {
    long total = combined.filter(line -> line.contains("SAVINGS")).count();
}
// Both files are closed when the combined stream is closed

What should a senior engineer know?

  • ofNullable is preferred over filter(Objects::nonNull) when the nullable value comes from a method call rather than a stream element.
  • concat preserves encounter order: all elements from the first stream, then all from the second.
  • Both are factory methods on the Stream interface: no utility class needed.

What are common interview follow-up questions?

  • Can Stream.of(null) work? (No, it throws NullPointerException. Use ofNullable for nullable values.)
  • Is concat eager or lazy? (Lazy. Neither stream is consumed until a terminal operation triggers.)
  • Does concat handle parallel streams? (Yes. The resulting stream can be parallelized.)

Visual Overview

DIAGRAM
  Stream.ofNullable:
  ┌───────────────┐     ┌──────────────┐
  │ value = null  │ ──▶ │ Stream.empty()│
  └───────────────┘     └──────────────┘
  ┌───────────────┐     ┌─────────────────┐
  │ value = "SAV" │ ──▶ │ Stream.of("SAV")│
  └───────────────┘     └─────────────────┘

  Stream.concat(a, b):
  Stream A: [1, 2, 3]
  Stream B: [4, 5, 6]
  concat:   [1, 2, 3, 4, 5, 6]  (lazy)

Key Takeaway

Stream.ofNullable (Java 9) returns a one-element stream if the value is non-null, or an empty stream if null. It replaces the verbose value != null ? Stream.of(value) : Stream.empty() ternary. The most common use is inside flatMap to safely include nullable values: trades.stream().flatMap(t -> Stream.ofNullable(t.getNote())) collects only non-null notes without NullPointerException.

Stream.concat lazily merges two streams end-to-end: elements from the first stream, then the second. Neither is consumed until a terminal operation triggers. For merging more than two streams, Stream.of(s1, s2, s3).flatMap(s -> s) is cleaner than nested concat calls.

In a banking consolidated statement service, three data sources may be null. Stream.ofNullable(cards).flatMap(Collection::stream) safely converts null to empty. Then Stream.of(s1, s2, s3).flatMap(s -> s) merges all sources into one stream.

Remember one sentence: ofNullable safely converts a nullable value to a zero-or-one element stream, and concat lazily merges two streams end-to-end.

---

Share
Use ← → arrow keys to navigate