# Streams

Course: [Java](https://www.devtutorials.ai/learn/java) · Section: Functional Programming
Source: https://www.devtutorials.ai/learn/java/functional-programming/streams

> Process collections with streams: create them, chain filter, map, sorted and more, finish with collect, reduce, count and match operations, and use parallel streams.

Think of a factory conveyor belt sorting fruit.

Apples roll onto the belt. One station removes the bruised ones. The next washes each apple. Another sorts them by size. At the end, a worker packs the good apples into a box.

Each station does one simple job, and the apples flow from one step to the next.

A Java stream works the same way. Your data flows through a chain of simple steps: remove what you do not want, transform each item, sort them, then collect the result.

In simple words: a stream lets you process data step by step by describing **what** to do, instead of writing the loop yourself.

## What You Will Learn

In this lesson, you will learn:

1. What a stream is and how it differs from a collection.
2. How to create streams from lists, arrays, values and ranges.
3. The intermediate operations `filter`, `map`, `sorted`, `distinct`, `limit` and `skip`.
4. The terminal operations `collect`, `forEach`, `reduce`, `count`, `findFirst`, `anyMatch` and `allMatch`.
5. How to build a stream pipeline from multiple steps.
6. What parallel streams are and when to use them.

## Start With a Working Example

Press **Run**.

```java runnable title="Loop vs stream for the same job"
import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> marks = List.of(72, 35, 88, 28, 64, 91);

        List<Integer> loopResult = new ArrayList<>();
        for (int m : marks) {
            if (m >= 40) {
                loopResult.add(m + 5);
            }
        }
        System.out.println("With a loop:   " + loopResult);

        List<Integer> streamResult = marks.stream()
                .filter(m -> m >= 40)
                .map(m -> m + 5)
                .toList();
        System.out.println("With a stream: " + streamResult);
    }
}
```

Expected output:

```text
With a loop:   [77, 93, 69, 96]
With a stream: [77, 93, 69, 96]
```

Both give the same answer: keep the passing marks, then add 5 to each.

The loop says **how**: make a list, loop, check, add. The stream says **what**: filter, then map, then turn into a list. It reads almost like a sentence.

### Try This

Add `.sorted()` between `.map(...)` and `.toList()`. The stream result comes out in order: `[69, 77, 93, 96]`.

## Introduction to Streams

A **stream** is a sequence of elements that you process with a chain of operations. Streams live in the `java.util.stream` package and were added in Java 8, together with lambdas.

A stream is **not** a collection:

| | Collection | Stream |
|---|---|---|
| Purpose | Stores data | Processes data |
| Holds elements? | Yes | No, they flow through it |
| Can be used again? | Yes, as often as you like | No, only once |
| Changes the source? | Can be changed with `add` and `remove` | Never changes its source |
| Style | You write the loop | You describe the steps |

Every stream has three parts:

1. A **source**: where the elements come from, such as a list.
2. Zero or more **intermediate operations**: steps that transform the stream, such as `filter` and `map`.
3. One **terminal operation**: the final step that produces a result, such as `toList` or `count`.

```text
 source  →  filter  →  map  →  sorted  →  collect
 (list)     \______ intermediate ______/   (terminal)
```

### Streams Are Lazy

Intermediate operations do **nothing** on their own. They only describe the work. The work happens when a terminal operation runs.

This lets Java be clever. For example, if you only want the first match, it stops as soon as it finds one instead of processing every element.

## Creating Streams

| Source | How | Example |
|---|---|---|
| A list or set | `collection.stream()` | `names.stream()` |
| Some values | `Stream.of(...)` | `Stream.of("A", "B", "C")` |
| An array | `Arrays.stream(array)` | `Arrays.stream(prices)` |
| A range of numbers | `IntStream.range(a, b)` | `IntStream.rangeClosed(1, 5)` |
| A rule that repeats | `Stream.iterate(start, rule)` | `Stream.iterate(1, n -> n * 2)` |

`IntStream`, `LongStream` and `DoubleStream` are special streams for primitive numbers. They have extra methods like `sum()` and `average()`.

```java runnable title="Five ways to create a stream"
import java.util.Arrays;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        List<String> cities = List.of("Pune", "Goa", "Ooty");
        System.out.println("From a list: " + cities.stream().count() + " cities");

        System.out.println("From values: " + Stream.of("red", "green", "blue").toList());

        int[] prices = {120, 45, 300};
        System.out.println("From an array, total: " + Arrays.stream(prices).sum());

        System.out.println("From a range: " + IntStream.rangeClosed(1, 5).boxed().toList());

        System.out.println("From a rule: " + Stream.iterate(1, n -> n * 2).limit(6).toList());
    }
}
```

Expected output:

```text
From a list: 3 cities
From values: [red, green, blue]
From an array, total: 465
From a range: [1, 2, 3, 4, 5]
From a rule: [1, 2, 4, 8, 16, 32]
```

`rangeClosed(1, 5)` includes `5`. `range(1, 5)` stops at `4`.

`boxed()` turns an `IntStream` of `int` values into a normal stream of `Integer` objects so it can become a `List`.

`Stream.iterate` would go on forever, so `limit(6)` stops it after six numbers.

## Intermediate Operations

An **intermediate operation** takes a stream and returns a **new stream**. Because each one returns a stream, you can chain them one after another.

### filter()

`filter(rule)` keeps only the elements where the rule is `true`. The rule is a `Predicate`.

### map()

`map(rule)` changes **each** element into something else. The rule is a `Function`. The new stream has the same number of elements, but they can be a different type.

```java runnable title="filter keeps some, map changes all"
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("asha", "ravi", "meena", "arjun", "divya");

        List<String> startsWithA = names.stream()
                .filter(n -> n.startsWith("a"))
                .toList();
        System.out.println("filter: " + startsWithA);

        List<String> capitals = names.stream()
                .map(String::toUpperCase)
                .toList();
        System.out.println("map to upper case: " + capitals);

        List<Integer> lengths = names.stream()
                .map(String::length)
                .toList();
        System.out.println("map to lengths: " + lengths);
    }
}
```

Expected output:

```text
filter: [asha, arjun]
map to upper case: [ASHA, RAVI, MEENA, ARJUN, DIVYA]
map to lengths: [4, 4, 5, 5, 5]
```

`filter` returned 2 of the 5 names. `map` returned all 5, each changed.

The last `map` turned a stream of `String` into a stream of `Integer`.

### sorted()

`sorted()` puts the elements in natural order. `sorted(comparator)` uses your own rule.

### distinct()

`distinct()` removes duplicates, keeping the first of each.

### limit()

`limit(n)` keeps only the **first** `n` elements.

### skip()

`skip(n)` throws away the **first** `n` elements and keeps the rest.

Together, `skip` and `limit` are handy for **pages**: `skip(10).limit(10)` gives the second page of ten items.

```java runnable title="sorted, distinct, limit and skip"
import java.util.Comparator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> scores = List.of(64, 91, 72, 91, 55, 88, 72, 40);

        System.out.println("sorted: " + scores.stream().sorted().toList());
        System.out.println("highest first: " + scores.stream().sorted(Comparator.reverseOrder()).toList());
        System.out.println("distinct: " + scores.stream().distinct().toList());
        System.out.println("top 3: " + scores.stream()
                .sorted(Comparator.reverseOrder())
                .limit(3)
                .toList());
        System.out.println("skip first 5: " + scores.stream().skip(5).toList());
    }
}
```

Expected output:

```text
sorted: [40, 55, 64, 72, 72, 88, 91, 91]
highest first: [91, 91, 88, 72, 72, 64, 55, 40]
distinct: [64, 91, 72, 55, 88, 40]
top 3: [91, 91, 88]
skip first 5: [88, 72, 40]
```

`distinct` kept the first `91` and the first `72` in their original positions.

"Top 3" is a classic pipeline: sort from high to low, then keep the first three.

| Operation | What it does | Number of elements out |
|---|---|---|
| `filter(rule)` | Keeps elements that pass the rule | Same or fewer |
| `map(rule)` | Changes every element | Same |
| `sorted()` | Puts elements in order | Same |
| `distinct()` | Removes duplicates | Same or fewer |
| `limit(n)` | Keeps the first `n` | At most `n` |
| `skip(n)` | Drops the first `n` | Fewer |

## Terminal Operations

A **terminal operation** ends the stream and produces a result: a collection, a number, a `boolean`, or an action like printing.

After a terminal operation, the stream is **used up**. Using it again throws an `IllegalStateException`.

### collect()

`collect(collector)` gathers the elements into a collection or other result. The **`Collectors`** class provides ready-made collectors:

| Collector | Gives you |
|---|---|
| `Collectors.toList()` | A `List` |
| `Collectors.toSet()` | A `Set` |
| `Collectors.joining(", ")` | One `String`, with a separator between elements |
| `Collectors.groupingBy(rule)` | A `Map` from each group to its elements |
| `Collectors.counting()` | The number of elements (used inside `groupingBy`) |

Since Java 16, `stream.toList()` is a shorter way to collect into a list that cannot be changed.

### forEach()

`forEach(action)` runs an action on every element. It returns nothing. It is usually used for printing. Read the code, then look at the output below it.

```java readonly title="collect into a list, a String and groups"
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<String> fruits = List.of("apple", "banana", "avocado", "cherry", "blueberry", "apricot");

        List<String> longOnes = fruits.stream()
                .filter(f -> f.length() > 6)
                .collect(Collectors.toList());
        System.out.println("Long names: " + longOnes);

        String line = fruits.stream()
                .map(String::toUpperCase)
                .collect(Collectors.joining(" | "));
        System.out.println("Joined: " + line);

        Map<Character, Long> byLetter = fruits.stream()
                .collect(Collectors.groupingBy(f -> f.charAt(0), TreeMap::new, Collectors.counting()));
        System.out.println("Count by first letter: " + byLetter);

        fruits.stream()
                .filter(f -> f.startsWith("b"))
                .forEach(f -> System.out.println("B fruit: " + f));
    }
}
```

Output:

```text
Long names: [avocado, blueberry, apricot]
Joined: APPLE | BANANA | AVOCADO | CHERRY | BLUEBERRY | APRICOT
Count by first letter: {a=3, b=2, c=1}
B fruit: banana
B fruit: blueberry
```

`groupingBy` sorted the fruits into groups by their first letter, then counted each group. Passing `TreeMap::new` keeps the letters in sorted order.

### reduce()

`reduce` combines all the elements into **one** value, two at a time.

```text
reduce(startValue, (total, element) -> newTotal)
```

It starts with the start value, then combines it with each element in turn. Use it for totals, products, maximums and joining. Read the code, then look at the output below it.

```java readonly title="reduce folds many values into one"
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> prices = List.of(120, 45, 300, 80);

        int total = prices.stream().reduce(0, (sum, p) -> sum + p);
        System.out.println("Total: " + total);

        int total2 = prices.stream().reduce(0, Integer::sum);
        System.out.println("Total with Integer::sum: " + total2);

        int biggest = prices.stream().reduce(0, Integer::max);
        System.out.println("Biggest: " + biggest);

        int product = List.of(2, 3, 4).stream().reduce(1, (a, b) -> a * b);
        System.out.println("2 x 3 x 4 = " + product);
    }
}
```

Output:

```text
Total: 545
Total with Integer::sum: 545
Biggest: 300
2 x 3 x 4 = 24
```

For the total, `reduce` works like this: `0 + 120 = 120`, then `120 + 45 = 165`, then `165 + 300 = 465`, then `465 + 80 = 545`.

For a product, the start value is `1`, not `0`, because anything times zero is zero.

### count()

`count()` returns how many elements reached the end of the stream, as a `long`.

### findFirst()

`findFirst()` returns the first element, wrapped in an **`Optional`**.

An `Optional` is a box that either holds one value or is empty. It exists because the stream might have no elements at all. Use `orElse(fallback)` to get the value, or a fallback if the box is empty.

### anyMatch()

`anyMatch(rule)` returns `true` if **at least one** element passes the rule.

### allMatch()

`allMatch(rule)` returns `true` if **every** element passes the rule.

There is also `noneMatch(rule)`, which returns `true` if **no** element passes.

```java runnable title="count, findFirst, anyMatch and allMatch"
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> ages = List.of(14, 22, 17, 35, 19);

        long adults = ages.stream().filter(a -> a >= 18).count();
        System.out.println("Adults: " + adults);

        int firstAdult = ages.stream().filter(a -> a >= 18).findFirst().orElse(-1);
        System.out.println("First adult's age: " + firstAdult);

        int firstSenior = ages.stream().filter(a -> a >= 60).findFirst().orElse(-1);
        System.out.println("First senior's age: " + firstSenior);

        System.out.println("Any under 15? " + ages.stream().anyMatch(a -> a < 15));
        System.out.println("All over 10? " + ages.stream().allMatch(a -> a > 10));
        System.out.println("All adults? " + ages.stream().allMatch(a -> a >= 18));
    }
}
```

Expected output:

```text
Adults: 3
First adult's age: 22
First senior's age: -1
Any under 15? true
All over 10? true
All adults? false
```

There is no age of 60 or more, so `findFirst()` returned an empty `Optional`, and `orElse(-1)` gave `-1`.

`anyMatch` and `allMatch` stop early. `anyMatch` stops at the first match. `allMatch` stops at the first failure.

| Operation | Returns | Example result |
|---|---|---|
| `collect(...)` / `toList()` | A collection or other result | `[22, 35, 19]` |
| `forEach(action)` | Nothing | Prints lines |
| `reduce(start, rule)` | One combined value | `545` |
| `count()` | A `long` | `3` |
| `findFirst()` | An `Optional` | `Optional[22]` |
| `anyMatch(rule)` | `boolean` | `true` |
| `allMatch(rule)` | `boolean` | `false` |

A stream can only be used **once**. After a terminal operation, calling another operation on the same stream throws `IllegalStateException: stream has already been operated upon or closed`. Create a new stream with `list.stream()` each time.

## Stream Pipelines

A **pipeline** is the whole chain: a source, some intermediate operations, and one terminal operation.

Good pipelines read top to bottom like a recipe. Put each step on its own line, starting with a dot.

```java runnable title="A full pipeline over a list of orders"
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Order> orders = List.of(
                new Order("Asha", "Books", 450, true),
                new Order("Ravi", "Phones", 18000, true),
                new Order("Meena", "Books", 300, false),
                new Order("Arjun", "Shoes", 2200, true),
                new Order("Divya", "Phones", 15500, true),
                new Order("Kiran", "Shoes", 1800, true));

        String bigBuyers = orders.stream()
                .filter(Order::paid)
                .filter(o -> o.amount() > 1000)
                .sorted((a, b) -> b.amount() - a.amount())
                .map(Order::customer)
                .collect(Collectors.joining(", "));
        System.out.println("Paid orders over 1000, biggest first: " + bigBuyers);

        int phoneRevenue = orders.stream()
                .filter(o -> o.category().equals("Phones"))
                .mapToInt(Order::amount)
                .sum();
        System.out.println("Phone revenue: " + phoneRevenue);

        double averagePaid = orders.stream()
                .filter(Order::paid)
                .mapToInt(Order::amount)
                .average()
                .orElse(0);
        System.out.println("Average paid order: " + averagePaid);
    }
}

record Order(String customer, String category, int amount, boolean paid) {
}
```

Expected output:

```text
Paid orders over 1000, biggest first: Ravi, Divya, Arjun, Kiran
Phone revenue: 33500
Average paid order: 7590.0
```

The first pipeline has five steps, and each does one simple thing: keep paid orders, keep big ones, sort biggest first, take the customer's name, join the names.

`mapToInt` turns the stream into an `IntStream`, which has `sum()` and `average()`.

### How Elements Flow

Elements do not go through each step as a whole batch. Each element travels down the **whole** pipeline before the next one starts. That is why `findFirst` and `limit` can stop early.

```java runnable title="Watch each element travel through the steps"
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> result = List.of("pen", "notebook", "bag", "calculator").stream()
                .filter(s -> {
                    System.out.println("filter: " + s);
                    return s.length() > 3;
                })
                .map(s -> {
                    System.out.println("  map: " + s);
                    return s.toUpperCase();
                })
                .limit(1)
                .toList();

        System.out.println("Result: " + result);
    }
}
```

Expected output:

```text
filter: pen
filter: notebook
  map: notebook
Result: [NOTEBOOK]
```

`pen` failed the filter, so it stopped there. `notebook` passed both steps. Then `limit(1)` was satisfied, so `bag` and `calculator` were never even looked at.

**Easy Way to Remember:** a pipeline is **from → what to keep → how to change → what to give back**. Read each line as one step of a recipe.

## Parallel Streams

A **parallel stream** splits the work across several CPU cores at the same time, then combines the results.

You make one with `list.parallelStream()`, or by adding `.parallel()` to a stream.

```text
long count = bigList.parallelStream()
        .filter(n -> n % 3 == 0)
        .count();
```

### When Parallel Streams Help

- The data is **large**: many thousands of elements or more.
- Each element needs **real work**, like heavy calculations.
- The steps are **independent**: one element does not depend on another.

### When They Do Not Help

- Small lists. Splitting and combining the work costs more than it saves.
- Steps that change a shared variable or collection. Several threads changing the same thing at once give wrong results.
- When the **order** of side effects matters. `forEach` on a parallel stream runs in an unpredictable order. Use `forEachOrdered` if order matters.

```java runnable title="Parallel and sequential give the same answer"
import java.util.stream.IntStream;

public class Main {
    public static void main(String[] args) {
        long sequential = IntStream.rangeClosed(1, 1000)
                .filter(n -> n % 7 == 0)
                .count();

        long parallel = IntStream.rangeClosed(1, 1000)
                .parallel()
                .filter(n -> n % 7 == 0)
                .count();

        int sum = IntStream.rangeClosed(1, 100).parallel().sum();

        System.out.println("Multiples of 7, sequential: " + sequential);
        System.out.println("Multiples of 7, parallel:   " + parallel);
        System.out.println("Sum of 1 to 100 (parallel): " + sum);
    }
}
```

Expected output:

```text
Multiples of 7, sequential: 142
Multiples of 7, parallel:   142
Sum of 1 to 100 (parallel): 5050
```

Counting and summing give the same answer either way, because they do not depend on order.

On a list this small, the parallel version is not faster. The benefit only shows on large amounts of work.

The in-browser runtime in this course runs your code on a single thread, so a parallel stream here works but cannot actually run in parallel. On a real JVM with several cores, the same code splits the work across them.

| | Sequential stream | Parallel stream |
|---|---|---|
| Created with | `list.stream()` | `list.parallelStream()` or `.parallel()` |
| Uses | One thread | Several threads |
| Element order in `forEach` | Predictable | Unpredictable |
| Best for | Most everyday work | Large data with heavy, independent work |
| Risk | None extra | Wrong results if steps share changing state |

## Common mistakes

The following mistakes are common when working with streams. Each row shows what you see, why it happens and how to fix it.

| Symptom | Cause | Fix |
|---|---|---|
| A pipeline seems to do nothing | No terminal operation | End with `toList`, `count`, `forEach` or similar |
| `stream has already been operated upon or closed` | A stream was used twice | Create a new stream for each pipeline |
| The original list is unchanged | Streams never change their source | Store the result in a new variable |
| `UnsupportedOperationException` | Adding to a list from `toList()` | Copy it into `new ArrayList<>(...)` |
| `reduce` always returns 0 | Start value `0` used for multiplying | Use `1` for products |
| `NoSuchElementException` from `get()` on an `Optional` | `findFirst` found nothing | Use `orElse(...)` |

## What to Remember

- A stream processes data in steps. It does not store data, and it never changes its source.
- A pipeline has a source, intermediate operations and one terminal operation. Nothing runs until the terminal operation.
- `filter` keeps some elements, `map` changes each one, `sorted` orders them, `distinct` removes repeats, `limit` and `skip` cut the stream.
- `collect` and `toList` gather results, `forEach` acts on each element, `reduce` combines them, and `count` counts them.
- `findFirst` returns an `Optional`, and `anyMatch` / `allMatch` answer yes-or-no questions.
- A stream can be used only once.
- Parallel streams split work across cores. Use them only for large, independent work.

Next: File Handling
