Q15 of 40 · Core Java
Explain the Stream API with a realistic data transformation example.
Short answer
Short answer: The Stream API provides a declarative pipeline for processing sequences of elements: source → zero-or-more intermediate operations (filter, map, sorted, flatMap) → one terminal operation (collect, forEach, reduce, count). Streams are lazy — intermediate operations don't execute until a terminal operation is called.
Detail
A Stream<T> is not a data structure — it's a pipeline that describes a sequence of transformations. You get a stream from a source (collection, array, Stream.of(), Files.lines()), chain intermediate operations, and end with one terminal operation that triggers execution.
Lazy evaluation: intermediate operations like filter() and map() are lazy — they build a description of work but do nothing until a terminal operation is called. This means a filter().map().findFirst() pipeline can short-circuit after finding the first match, avoiding processing the rest of the stream.
Key intermediate operations:
filter(Predicate)— keep elements matching a predicatemap(Function)— transform each element to a new typeflatMap(Function)— flatten a stream-of-streams to a single streamsorted()/sorted(Comparator)— sort elementsdistinct()— remove duplicateslimit(n)/skip(n)— windowing
Key terminal operations:
collect(Collectors.toList())/toSet()/toMap()— materialise into a collectionforEach(Consumer)— side-effect for each elementreduce(BinaryOperator)— fold to a single valuecount(),findFirst(),anyMatch(),allMatch()— aggregation / short-circuit
Streams are not reusable — a stream can be consumed only once. If you need to iterate a result multiple times, collect to a list first.
For test data: streams excel at filtering test cases by tag, grouping scenarios by category, or transforming a list of raw API responses into typed domain objects.
// EXAMPLE
StreamExample.java
record TestCase(String id, String tag, boolean passed, long durationMs) {}
List<TestCase> results = loadTestResults();
// Collect IDs of failed smoke tests sorted by duration descending
List<String> failedSmokeIds = results.stream()
.filter(tc -> !tc.passed()) // keep failures
.filter(tc -> "smoke".equals(tc.tag())) // smoke tag only
.sorted(Comparator.comparingLong(TestCase::durationMs).reversed())
.map(TestCase::id) // extract id
.toList(); // Java 16+ (immutable list)
// Count failures per tag
Map<String, Long> failuresByTag = results.stream()
.filter(tc -> !tc.passed())
.collect(Collectors.groupingBy(TestCase::tag, Collectors.counting()));
// Average duration of passing tests (OptionalDouble for empty stream)
OptionalDouble avgPassMs = results.stream()
.filter(TestCase::passed)
.mapToLong(TestCase::durationMs)
.average();
// flatMap — flatten a list-of-lists into one stream
List<String> allSteps = results.stream()
.flatMap(tc -> getSteps(tc.id()).stream())
.distinct()
.toList();