Back to Stream API Fundamentals
Q8Question 8 of 8

Stream vs Collection: key differences?

Key Concepts
  • A Collection stores elements in memory (eager, reusable, mutable, has size and random access).
  • A Stream processes elements on demand (lazy, single-use, immutable pipeline, no storage, no size).
  • Collections use external iteration (you control the loop); streams use internal iteration (stream controls traversal).
  • Collections are about HAVING data; streams are about PROCESSING data.
  • Streams cannot modify the source; they produce new streams or results via collect/reduce.
  • Streams support built-in parallelism via parallelStream(); collection parallelism requires manual threading.
  • The typical pattern: collection.stream() -> pipeline -> collect() -> back to collection.

Definition

A Collection stores data (eager, reusable, mutable); a Stream processes data (lazy, single-use, immutable pipeline): Collections hold, Streams flow.
  • Storage: Collection stores elements in memory. Stream stores nothing and processes on the fly.
  • Evaluation: Collection is eager (all elements exist). Stream is lazy (computed on demand).
  • Reuse: Collection is reusable (iterate many times). Stream is single-use (consumed after terminal).
  • Mutability: Collection supports add, remove, set. Stream cannot modify the source.
  • Iteration: Collection uses external iteration (your loop). Stream uses internal iteration (stream controls).
  • Parallelism: Collection requires manual threading. Stream provides built-in parallelStream().
  • Purpose: Collection is the data layer. Stream is the computation layer.

A Collection is the bank vault at National Bank of India: it stores cash physically. You can count it (size), add more, remove some, and visit it multiple times. A Stream is the cash-counting machine: cash flows through it, gets counted, sorted, and filtered, but the machine stores nothing. Once cash passes through, the machine resets. The vault is about HAVING data. The machine is about PROCESSING data.

Understanding the Concepts

What are the differences between a Stream and a Collection?

Comparison table

| Aspect | Collection | Stream |
|--------|-----------|--------|
| Storage | Stores elements in memory | Stores nothing |
| Evaluation | Eager: all elements exist | Lazy: computed on demand |
| Reuse | Reusable: iterate many times | Single-use: consumed after terminal |
| Mutability | Add, remove, modify | Cannot modify source |
| Size | Known: size() method | Unknown: no size() |
| Random access | Yes: get(index) | No: sequential or parallel |
| Iteration | External (your loop) | Internal (stream controls) |
| Parallelism | Manual threads | Built-in parallelStream() |

Code comparison

JAVA
package com.bank.service;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

public class StreamVsCollection {
    record Account(String id, BigDecimal balance, boolean active) {}

    public static void main(String[] args) {
        // COLLECTION: stores, accesses, modifies
        List<Account> accounts = new ArrayList<>(List.of(
            new Account("SAV2001", new BigDecimal("125000"), true),
            new Account("SAV2002", new BigDecimal("8000"), false),
            new Account("CUR3001", new BigDecimal("250000"), true)
        ));
        System.out.println("Size: " + accounts.size());     // 3
        System.out.println("First: " + accounts.get(0).id()); // SAV2001

        // STREAM: processes, transforms, aggregates
        BigDecimal activeTotal = accounts.stream()
            .filter(Account::active)
            .map(Account::balance)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        System.out.println("Active total: Rs." + activeTotal);

        Map<Boolean, List<Account>> partitioned = accounts.stream()
            .collect(Collectors.partitioningBy(Account::active));
        System.out.println("Active: " + partitioned.get(true).size());
    }
}
TEXT
Size: 3
First: SAV2001
Active total: Rs.375000
Active: 2

How do they work together?

The typical pattern is Collection -> Stream -> pipeline -> collect() -> Collection.

JAVA
// JPA returns a Collection from the database
List<Account> accounts = repository.findAll();

// Process with Stream
List<String> activeIds = accounts.stream()
    .filter(Account::active)
    .map(Account::id)
    .sorted()
    .toList();

// Return as DTO
return new ReportDTO(activeIds);

Why are streams single-use?

JAVA
import java.util.stream.Stream;

Stream<String> ids = accounts.stream().map(Account::id);
ids.count(); // terminal operation consumes the stream

// WRONG: reusing consumed stream
// ids.toList(); // IllegalStateException: stream has already been operated upon!

// CORRECT: create a new stream
accounts.stream().map(Account::id).toList();

Why is laziness important?

Lazy evaluation means intermediate operations build a pipeline but do not process any data until a terminal operation triggers it. This enables short-circuiting.

JAVA
// Without laziness: filter ALL, then find first
// With laziness: stop as soon as the first match passes
Optional<Account> first = accounts.stream()
    .filter(Account::active)
    .findFirst(); // stops at the first active account

What are the iteration differences?

JAVA
// EXTERNAL iteration (Collection): you control the loop
for (Account a : accounts) {
    if (a.active()) { System.out.println(a.id()); }
}

// INTERNAL iteration (Stream): stream controls traversal
accounts.stream()
    .filter(Account::active)
    .map(Account::id)
    .forEach(System.out::println);

Internal iteration allows the stream to optimise traversal order and parallelise transparently.

What about mutability?

JAVA
// Collection: supports add, remove, set
accounts.add(new Account("FD5001", new BigDecimal("500000"), true));
accounts.remove(0);

// Stream: cannot modify the source
// accounts.stream().forEach(a -> accounts.remove(a)); // ConcurrentModificationException!

What should a senior engineer know?

  • Collections and Streams are complementary. Neither replaces the other.
  • Use Collection when you need to store, index, search, or modify data.
  • Use Stream when you need to filter, transform, aggregate, or parallelise processing.
  • In a typical Spring service, JPA returns a Collection, you process with Streams, and return the result as a DTO.
  • Stream operations do not modify the source. The original Collection remains the single source of truth.

Visual Overview

DIAGRAM
  Collection (stores)            Stream (processes)
  ┌──────────────────┐           ┌──────────────────┐
  │ List<Account>    │ .stream() │ filter(active)   │
  │ size: 3          │─────────→ │ map(balance)     │
  │ get(0): SAV2001  │           │ reduce(add)      │
  │ add, remove, set │           │ = Rs.375000      │
  └──────────────────┘           └──────────────────┘
  Stores data                    Processes data
  Eager, reusable               Lazy, single-use
  Mutable                       Immutable pipeline

Key Takeaway

Collections store data: they have size, random access, add, remove, and are reusable. Streams process data: they are lazy, single-use, have no storage, and cannot modify the source.

They work together: Collection stores the data (data layer), Stream processes the data (computation layer). The typical pattern is collection.stream() to pipeline to collect() back to collection.

Remember one sentence: Collections hold data, Streams process data, and they work together with collection.stream() into pipeline into collect().

---

Share
Use ← → arrow keys to navigate