The Stream API (introduced in Java 8) provides a functional, declarative way to process data. A Stream represents a sequence of elements supporting aggregate operations like:
- filtering
- mapping
- reducing
- sorting
- collecting
Streams do not store data; they operate on top of collections, arrays, or I/O channels.
| Property | Description |
|---|---|
| Not a data structure | Does not store elements; works on source data |
| Functional style | Uses lambdas; concise, readable |
| Lazy evaluation | Intermediate operations do not execute until terminal operation |
| Can be parallel | Use .parallelStream() |
| Once consumed, cannot be reused | Streams cannot be traversed twice |
List<Integer> list = List.of(1, 2, 3);
Stream<Integer> stream = list.stream();int[] arr = {1, 2, 3};
IntStream s = Arrays.stream(arr);Stream<String> stream = Stream.of("A", "B", "C");Stream<Integer> s = Stream.iterate(1, n -> n + 1);They transform a stream into another stream.
| Operation | Description |
|---|---|
filter() |
Select elements based on condition |
map() |
Transform elements |
sorted() |
Sorting |
distinct() |
Remove duplicates |
limit() |
Take first N elements |
skip() |
Skip first N elements |
flatMap() |
Flatten nested structures |
Intermediate operations do not trigger execution.
| Operation | Description |
|---|---|
forEach() |
Iterate and perform action |
collect() |
Gather results (List, Set, etc.) |
reduce() |
Reduce to single value |
count() |
Count elements |
findFirst() / findAny() |
Get element |
allMatch(), anyMatch(), noneMatch() |
Match conditions |
Once a terminal operation runs, the stream is consumed.
List<Integer> nums = List.of(10, 15, 20, 25);
List<Integer> even = nums.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());List<String> names = List.of("java", "spring", "docker");
List<String> upper = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());List<Integer> sorted = nums.stream()
.sorted()
.collect(Collectors.toList());Custom comparator:
names.stream().sorted((a, b) -> b.compareTo(a));int sum = nums.stream()
.reduce(0, (a, b) -> a + b);Or:
int sum = nums.stream().mapToInt(n -> n).sum();List<Integer> result = List.of(1, 2, 2, 3, 4, 4, 5).stream()
.distinct()
.limit(3)
.collect(Collectors.toList());Useful when dealing with nested structures.
List<List<Integer>> list = List.of(
List.of(1,2),
List.of(3,4)
);
List<Integer> flat = list.stream()
.flatMap(l -> l.stream())
.collect(Collectors.toList());Stream operations can run in parallel:
list.parallelStream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);- Large dataset
- CPU-heavy operations
- Order is not critical
- Small collections
- I/O operations
- Shared mutable state
List<Integer> list = stream.collect(Collectors.toList());Set<Integer> set = stream.collect(Collectors.toSet());Map<Integer, String> map = users.stream()
.collect(Collectors.toMap(u -> u.id, u -> u.name));Map<String, List<Employee>> grouped =
employees.stream().collect(Collectors.groupingBy(Employee::getDepartment));long count = stream.collect(Collectors.counting());For performance, Java provides:
IntStreamLongStreamDoubleStream
Example:
int sum = IntStream.of(1,2,3,4).sum();A stream pipeline has three steps:
- Source → Collection, array, IO
- Intermediate operations → filter, map, sorted
- Terminal operation → collect, reduce, forEach
Example:
List<Integer> result =
nums.stream() // source
.filter(n -> n > 5) // intermediate
.map(n -> n * 2) // intermediate
.collect(Collectors.toList()); // terminal| Feature | Stream | Loop |
|---|---|---|
| Style | Functional | Imperative |
| Parallel support | Easy | Complex |
| Mutability | Avoided | Mutations common |
| Readability | Concise | Verbose |
| Reusability | No | Yes |
int sum = 0;
list.stream().forEach(n -> sum += n); // ❌ not thread-safeUse reduce instead.
Stream<Integer> s = list.stream();
s.forEach(System.out::println);
s.forEach(System.out::println); // ❌ error — stream already consumedmap()→ transforms each elementflatMap()→ flattens nested streams into one stream
Intermediate operations run only when a terminal operation exists → better performance.
No, streams are non-mutating.
stream()→ sequential executionparallelStream()→ parallel execution using ForkJoinPool
No, stream is consumed after terminal operation.
-
Streams provide a clean functional approach to processing data.
-
Support filtering, mapping, reducing, sorting, and collecting.
-
Intermediate operations are lazy.
-
Terminal operations trigger computation.
-
Streams are not data structures.
-
Great for parallel processing but must be used wisely.