Nulls in a Stream

Streams · lesson 33 of 42 · 3 min read

Where a null actually blows up in a pipeline, and the cheapest place to remove it.

Open this lesson in the learning hub

Key points

  • A stream happily carries nulls. Nothing complains until an op dereferences one, far from where it came in.
  • filter(Objects::nonNull) as the first op is the cheapest fix, and it documents the intent.
  • Collectors.toMap throws on a null value; groupingBy throws on a null key.
  • Stream.ofNullable(x) (Java 9+) gives an empty stream for null, so flatMap deletes it.
  • Better still, stop producing the null: return an empty list or an Optional from the method above.

Example

import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<String> raw = Arrays.asList("ada", null, "grace", null, "alan");

        System.out.println("carries nulls : " + raw.stream().count());
        System.out.println("cleaned       : " + raw.stream().filter(Objects::nonNull).toList());
        System.out.println("lengths       : " + raw.stream().filter(Objects::nonNull)
                .map(String::length).toList());

        // Stream.ofNullable turns a null into an empty stream, so flatMap deletes it.
        System.out.println("ofNullable    : " + raw.stream().flatMap(Stream::ofNullable).toList());

        // toMap explodes on a null VALUE, even when the key is perfectly fine.
        try {
            raw.stream().filter(Objects::nonNull)
                    .collect(Collectors.toMap(s -> s, s -> s.equals("grace") ? null : s));
        } catch (NullPointerException e) {
            System.out.println("toMap         : NullPointerException, null value");
        }

        // groupingBy explodes on a null KEY instead.
        try {
            raw.stream().collect(Collectors.groupingBy(s -> s));
        } catch (NullPointerException e) {
            System.out.println("groupingBy    : NullPointerException, null key");
        }

        // A map that does tolerate null values: build it yourself.
        Map<String, String> lenient = new TreeMap<>();
        raw.stream().filter(Objects::nonNull)
                .forEach(s -> lenient.put(s, s.equals("grace") ? null : s));
        System.out.println("lenient       : " + lenient);
    }
}

Drop nulls at the top of the pipeline, where the stack trace still points at the cause.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Streams course, and every lesson in it is listed on the Streams contents page.