Skip to content

The Filter That Never Ran

This program compiles and runs, and reports zero failures when there was one — because a filter with no terminal operation isn't a bug that crashes, it's a pipeline that silently never executes.

3 min read

This program compiles and runs. Read first, then run.

public class BugHunt16 {
    public static void main(String[] args) {
        List<String> results = Arrays.asList("PASS", "FAIL", "PASS");
        results.stream().filter(r -> r.equals("FAIL"));
        System.out.println("Failures reported: none");
        Stream<String> s = results.stream();
        System.out.println("count once: " + s.count());
        try {
            System.out.println("count twice: " + s.count());
        } catch (IllegalStateException e) {
            System.out.println("count twice: " + e.getMessage());
        }
        Optional<String> pay = results.stream().filter(r -> r.equals("PAY")).findFirst();
        try {
            System.out.println("value: " + pay.get());
        } catch (Exception e) {
            System.out.println("get() on empty: " + e.getClass().getSimpleName());
        }
    }
}
Failures reported: none
count once: 3
count twice: stream has already been operated upon or closed
get() on empty: NoSuchElementException

Three defects, one per rule in this chapter.

results.stream().filter(...) on its own line did nothing at all. filter is an intermediate operation, so with no terminal operation the pipeline never ran. There was one FAIL in the data and the program cheerfully reported none. This is the same shape as Chapter 14's ignored trim() — a call whose result was discarded — and it is silent in exactly the same way.

The stream was consumed by the first count(), and the second call threw IllegalStateException with a message that says so plainly. A stream is single-use. If you need two results from the same data, either start two streams from the collection or collect once into a list and work from that.

pay.get() on an empty Optional threw NoSuchElementException. Optional protected nothing here, because get() was called without checking. That is Optional used as decoration. Use orElse, ifPresent, or check isPresent first — otherwise you have swapped a NullPointerException for a differently-named one.

Checkpoint

  1. What is a functional interface, and what does a lambda supply?
  2. Name the four common functional interfaces and the method each one declares.
  3. What is the difference between an intermediate and a terminal operation, and what happens with no terminal one?
  4. Why does the second count() on the same stream fail?
  5. Give the three safe ways to read an Optional, and the one to avoid.
  6. Name two situations where a plain loop is the better choice.

Increasingly common for automation roles: what a functional interface is (an interface with exactly one abstract method, which a lambda can implement); the common ones and their methods (Predicate/test, Function/apply, Supplier/get, Consumer/accept); the difference between intermediate and terminal operations (intermediate ones describe a step and return a stream; terminal ones produce a result and trigger execution); whether a stream can be reused (no — a second terminal operation throws IllegalStateException); what problem Optional solves (it makes a possibly-absent value explicit rather than returning null); and whether map changes the source collection (no — streams do not modify the source).

You can pass behavior to a method. You can write a lambda, recognize which functional interface it fits, and use method references where they read more clearly. You can build stream pipelines that filter, transform, count, and collect, and you know that nothing runs until a terminal operation asks for a result. You can handle a possibly-absent value with Optional instead of a null check, and you can say why the shortest version is not always the right one.

Most importantly, you built a waitUntil that takes a condition as a value and re-evaluates it. That is not an analogy for an explicit wait — it is the mechanism, and you now understand the most-used feature in Selenium from the inside.

Your framework is nearly complete. It is organized, it reads its data from outside, it bends at the joints, and it can express its logic concisely. There is one thing left, and it is the one that breaks suites in production rather than in development. Right now everything you have written runs one test at a time. Then your team turns on parallel execution to get the nightly run under an hour, and a suite that passed for months starts failing in ways nobody can reproduce — different tests each run, on a machine nobody changed. The cause is a Core Java idea, not a Selenium one, and the fix is a single class. That is Chapter 17.

This article is part of Automation Foundations, in the Automation Engineering track. See the full sequence to find what comes next.