Definition
- 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
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());
}
}
Size: 3
First: SAV2001
Active total: Rs.375000
Active: 2
How do they work together?
The typical pattern is Collection -> Stream -> pipeline -> collect() -> Collection.
// 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?
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.
// 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?
// 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?
// 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
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().
---