Skip to content
  • Streams API: Intermediate & Terminal Operations · Java

Streams API: Intermediate & Terminal Operations

LearnViaNotes @learnvianotes· 5 min read

A stream is a lazy pipeline of operations over a sequence of elements — declare what to compute, and the library does the looping.

Source (list.stream()) → Intermediate ops (filter, map…) — lazy → Terminal op (collect, sum…) — triggers work → Result

A first pipeline

Java
import java.util.*;

public class Main {
    public static void main(String[] args) {
        List<String> words = List.of("stream", "java", "lambda", "api", "collector", "map");

        List<String> result = words.stream()
                .filter(w -> w.length() > 3)      // keep long words
                .map(String::toUpperCase)         // transform
                .sorted()                         // natural order
                .toList();                        // terminal (Java 16+)
        System.out.println(result);

        // the imperative equivalent
        List<String> loop = new ArrayList<>();
        for (String w : words) if (w.length() > 3) loop.add(w.toUpperCase());
        Collections.sort(loop);
        System.out.println(loop.equals(result));
    }
}
Output
[COLLECTOR, JAVA, LAMBDA, STREAM]
true

Creating streams

SourceExample
Collectionlist.stream(), set.parallelStream()
ArrayArrays.stream(arr), Stream.of(1, 2, 3)
Numeric rangeIntStream.range(0, 5) (end exclusive), rangeClosed(1, 5)
GeneratedStream.iterate(1, x -> x * 2), Stream.generate(supplier) — infinite, use limit
Bounded iterate (9+)Stream.iterate(1, x -> x < 100, x -> x * 2)
Othersstr.chars(), Files.lines(path), map.entrySet().stream(), Stream.empty()

Intermediate operations (lazy, return a Stream)

OperationWhat it doesStateful?
filter(pred)keep elements matchingNo
map(fn) / mapToInt(fn)transform each element (1 → 1)No
flatMap(fn)each element → a stream, flattened (1 → many)No
peek(action)look at elements as they pass (debugging)No
distinct()drop duplicates (uses equals/hashCode)Yes
sorted() / sorted(cmp)sort — must see all elements firstYes
limit(n) / skip(n)first n / drop first n (short-circuit limit)Yes (count)
takeWhile / dropWhile (9+)prefix while predicate holdsNo
Java
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4), List.of(4, 5));
        System.out.println(nested.stream().flatMap(List::stream).toList());
        System.out.println(nested.stream().flatMap(List::stream).distinct().skip(1).limit(3).toList());

        System.out.println(Stream.of("b", "a", "c").sorted(Comparator.reverseOrder()).toList());
        System.out.println(Stream.iterate(1, x -> x * 2).limit(6).toList());
        System.out.println(IntStream.rangeClosed(1, 10).takeWhile(i -> i < 5).boxed().toList());
        System.out.println("hello".chars().filter(c -> c == 'l').count());
    }
}
Output
[1, 2, 3, 4, 4, 5]
[2, 3, 4]
[c, b, a]
[1, 2, 4, 8, 16, 32]
[1, 2, 3, 4]
2

Laziness: nothing runs until the terminal op

Java
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        Stream<Integer> pipeline = Stream.of(1, 2, 3, 4, 5, 6)
                .peek(x -> System.out.println("saw " + x))
                .filter(x -> x % 2 == 0)
                .map(x -> x * 10);
        System.out.println("pipeline built, nothing printed yet");

        Integer first = pipeline.findFirst().orElseThrow();   // terminal op
        System.out.println("first = " + first);
    }
}
Output
pipeline built, nothing printed yet
saw 1
saw 2
first = 20

Terminal operations

OperationReturnsNotes
forEach(action)voidorder not guaranteed on parallel streams (forEachOrdered)
collect(collector) / toList()a collection / valuesee Collectors topic
reduce(identity, op)Tfold into one value
count(), min(cmp), max(cmp)long, Optional<T>min/max are Optional — stream may be empty
sum(), average(), summaryStatistics()number / OptionalDoubleonly on IntStream/LongStream/DoubleStream
anyMatch, allMatch, noneMatchbooleanshort-circuit; allMatch on empty = true
findFirst(), findAny()Optional<T>findAny is faster on parallel streams
toArray()arraytoArray(String[]::new) for a typed array
Java
import java.util.*;
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        List<Integer> nums = List.of(4, 8, 15, 16, 23, 42);

        System.out.println(nums.stream().reduce(0, Integer::sum));
        System.out.println(nums.stream().reduce(Integer::max).get());
        System.out.println(nums.stream().mapToInt(Integer::intValue).sum());
        System.out.println(nums.stream().mapToInt(i -> i).average().getAsDouble());
        System.out.println(nums.stream().max(Comparator.naturalOrder()).get());
        System.out.println(nums.stream().filter(n -> n > 100).count());
        System.out.println(nums.stream().anyMatch(n -> n % 2 == 1) + " " + nums.stream().allMatch(n -> n > 0));
        IntSummaryStatistics st = nums.stream().mapToInt(i -> i).summaryStatistics();
        System.out.println(st.getMin() + ".." + st.getMax() + " avg " + st.getAverage());
        String[] arr = Stream.of("x", "y").toArray(String[]::new);
        System.out.println(Arrays.toString(arr));
    }
}
Output
108
42
108
18.0
42
0
true true
4..42 avg 18.0
[x, y]

Primitive streams and boxing

IntStream, LongStream and DoubleStream avoid boxing and add sum, average, range. Convert with mapToInt / mapToObj / boxed().

Java
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        System.out.println(IntStream.range(0, 5).sum());
        System.out.println(IntStream.range(0, 5).boxed().toList());
        System.out.println(IntStream.rangeClosed(1, 5).mapToObj(i -> "#" + i).collect(Collectors.joining(",")));
        System.out.println(IntStream.of(3, 1, 2).map(i -> i * i).sorted().boxed().toList());
    }
}
Output
10
[0, 1, 2, 3, 4]
#1,#2,#3,#4,#5
[1, 4, 9]

Streams are single-use

Java
import java.util.stream.*;

public class Main {
    public static void main(String[] args) {
        Stream<String> s = Stream.of("a", "b");
        System.out.println(s.count());
        try {
            s.count();                        // reuse → exception
        } catch (IllegalStateException e) {
            System.out.println(e.getMessage());
        }
    }
}
Output
2
stream has already been operated upon or closed

Parallel streams — use with care

  • list.parallelStream() or .parallel() splits the work across the common ForkJoinPool (cores − 1 threads).

  • Worth it only for large, CPU-bound, easily splittable data (ArrayList, arrays, ranges) — not LinkedList or I/O.

  • Lambdas must be stateless and non-interfering: never mutate a shared ArrayList from forEach — use collect.

  • reduce needs an associative operation and a true identity (0 for sum), or results change with the split.

  • Order-dependent ops (findFirst, limit, forEachOrdered) cost extra in parallel; findAny / unordered() are cheaper.

Intermediate vs terminal operations?

map vs flatMap?

Stateless vs stateful intermediate operations?

Can a stream be reused? Does it modify its source?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report