Home/Learn/Java A–Z/Streams API

Streams API

Intermediate
Streams & Functional Java

Process collections declaratively with filter, map, reduce, and dozens of other operations — lazy, composable, and optionally parallel.

Overview

A Stream is a sequence of elements supporting sequential and parallel bulk operations. Streams are lazy: intermediate operations (filter, map, sorted…) are not evaluated until a terminal operation (collect, count, forEach…) is called. This enables the JVM to fuse multiple operations in a single pass. Streams do not store data — they are a pipeline over a source (collection, array, I/O). Once consumed by a terminal operation, a stream cannot be reused.

Creating Streams & Intermediate Operations

Sources: collection.stream(), Arrays.stream(arr), Stream.of(...), Stream.iterate(...), Stream.generate(...), Files.lines(path).

Intermediate operations (lazy, return Stream): filter(Predicate) — keep matching elements map(Function) — transform each element flatMap(Function) — flatten nested streams distinct() — remove duplicates via equals sorted() / sorted(Comparator) — sort limit(n) — cap at n elements skip(n) — skip first n elements peek(Consumer) — inspect without altering (debug)

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

public class StreamIntermediate {
    public static void main(String[] args) {
        List<String> words = List.of("hello","world","java","streams","api","java");

        // filter + distinct + sorted + limit
        List<String> result = words.stream()
            .filter(w -> w.length() > 3)    // hello, world, java, streams, java
            .distinct()                      // hello, world, java, streams
            .sorted()                        // hello, java, streams, world
            .limit(3)                        // hello, java, streams
            .collect(Collectors.toList());
        System.out.println(result);

        // map — transform each element
        List<Integer> lengths = words.stream()
            .map(String::length)
            .distinct()
            .sorted()
            .collect(Collectors.toList());
        System.out.println(lengths); // [3, 4, 5, 7]

        // flatMap — flatten List<List<T>> to Stream<T>
        List<List<Integer>> nested = List.of(List.of(1,2), List.of(3,4), List.of(5));
        List<Integer> flat = nested.stream()
            .flatMap(Collection::stream)
            .collect(Collectors.toList());
        System.out.println(flat); // [1, 2, 3, 4, 5]

        // peek — debug intermediate values (doesn't consume)
        long count = words.stream()
            .peek(w -> System.out.print("before: " + w + " "))
            .filter(w -> w.startsWith("j"))
            .peek(w -> System.out.print("after: " + w + " "))
            .count();
        System.out.println("
Count: " + count); // 2
    }
}

Terminal Operations & reduce

Terminal operations consume the stream and produce a result: collect(Collector) — accumulate into collection/map/string count() — number of elements forEach(Consumer) — side effect per element findFirst() / findAny() — Optional of first/any match anyMatch / allMatch / noneMatch — short-circuit boolean min / max — Optional of boundary element reduce(identity, BinaryOperator) — fold all elements toArray() — Object[] or typed array

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

public class TerminalOps {
    public static void main(String[] args) {
        List<Integer> nums = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // count, min, max
        System.out.println(nums.stream().count());                    // 10
        System.out.println(nums.stream().max(Integer::compareTo));    // Optional[10]
        System.out.println(nums.stream().min(Integer::compareTo));    // Optional[1]

        // reduce — fold: sum = 0+1+2+...+10
        int sum = nums.stream().reduce(0, Integer::sum);
        System.out.println(sum); // 55

        // reduce without identity — returns Optional
        Optional<Integer> product = nums.stream().reduce((a, b) -> a * b);
        System.out.println(product.orElse(0)); // 3628800

        // match operations — short-circuit
        System.out.println(nums.stream().anyMatch(n -> n > 9));   // true
        System.out.println(nums.stream().allMatch(n -> n > 0));   // true
        System.out.println(nums.stream().noneMatch(n -> n > 10)); // true

        // findFirst — Optional of first element matching filter
        Optional<Integer> first = nums.stream()
            .filter(n -> n % 3 == 0)
            .findFirst();
        System.out.println(first.orElse(-1)); // 3

        // Numeric streams — avoid boxing
        IntStream range = IntStream.rangeClosed(1, 5);
        System.out.println(range.sum());    // 15
        System.out.println(IntStream.rangeClosed(1, 5).average()); // OptionalDouble[3.0]

        // mapToInt for sum without boxing
        List<String> words = List.of("hi", "hello", "hey");
        int totalLen = words.stream().mapToInt(String::length).sum();
        System.out.println(totalLen); // 10
    }
}

Parallel Streams & Laziness

Call .parallelStream() or .stream().parallel() to enable multi-threaded processing. The common ForkJoinPool splits the source and merges results. Parallel streams shine on CPU-intensive, independent, stateless operations over large data sets.

Avoid parallel streams when: operations have side effects, the source is not efficiently splittable (LinkedList), the pipeline is short, or ordering must be preserved (use forEachOrdered).

Laziness: intermediate operations are not run until a terminal operation is called. Stream.iterate + limit is the canonical example — the limit stops generation early.

ParallelAndLazy.java
import java.util.stream.*;
import java.util.List;

public class ParallelAndLazy {
    public static void main(String[] args) {
        // Parallel stream — splits work across ForkJoin threads
        long count = LongStream.rangeClosed(1, 10_000_000)
            .parallel()
            .filter(n -> n % 2 == 0)
            .count();
        System.out.println(count); // 5000000

        // Laziness — iterate is infinite; limit stops it
        List<Integer> first10Squares = Stream.iterate(1, n -> n + 1)
            .map(n -> n * n)
            .limit(10)
            .collect(Collectors.toList());
        System.out.println(first10Squares); // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

        // Stream.generate — infinite random stream
        List<Double> randoms = Stream.generate(Math::random)
            .limit(5)
            .collect(Collectors.toList());
        System.out.println(randoms.size()); // 5

        // Parallel with ordering preserved
        List<Integer> nums = List.of(5, 3, 1, 4, 2);
        nums.parallelStream()
            .sorted()
            .forEachOrdered(System.out::print); // 12345 — always in order
        System.out.println();

        // Pitfall: parallel stream with shared mutable state
        // int[] counter = {0};
        // IntStream.range(0,1000).parallel().forEach(i -> counter[0]++); // RACE CONDITION
        // Use: IntStream.range(0,1000).parallel().count() instead
    }
}

Interactive Visualization

.source()
.filter()
.map()
.sorted()
.collect()
1
2
3
4
5
6
7
8
stream.filter(n → n%2==0).map(n → n*n).sorted().collect(toList())
Source: a stream of integers [1, 2, 3, 4, 5, 6, 7, 8].
1 / 5

Key Points to Remember

  • Streams are lazy — intermediate operations run only when a terminal operation is called
  • A stream cannot be reused after a terminal operation; create a new one from the source
  • flatMap flattens Stream<Stream<T>> or Stream<List<T>> into a single Stream<T>
  • Use mapToInt/mapToLong/mapToDouble + sum/average to avoid boxing overhead on numeric operations
  • Parallel streams use ForkJoinPool — avoid when operations have side effects or shared state
  • Stream.iterate() + limit() is the clean way to generate finite sequences

Practice Streams API in the Playground

Run and modify code directly in your browser - no setup needed.

Interview Questions

Sign in to ask Aria
1

What is the difference between intermediate and terminal stream operations?

EasyAmazon
2

What is lazy evaluation in Java Streams?

MediumGoogle
3

What is the difference between map() and flatMap()?

MediumMicrosoft
4

When should you NOT use parallel streams?

MediumOracle
5

What is the difference between findFirst() and findAny()?

MediumNetflix

Ask Aria about Streams API

Your personal AI tutor — ask anything about this concept