Backend Development 15 min read

Master Java Stream API: From Basics to Advanced Collection Operations

This article introduces Java Stream API introduced in JDK 8, explains its pipeline concept, and demonstrates practical operations such as traversal, filtering, deduplication, type conversion, collection‑to‑map transformations, pagination, matching, and parallel processing with clear code examples.

macrozheng
macrozheng
macrozheng
Master Java Stream API: From Basics to Advanced Collection Operations

1. Introduction

Since Java 8, the JDK added the Stream class to complement collections, offering a high‑level abstraction that lets developers manipulate data in a SQL‑like, declarative style without explicit loops.

Elements flow through a pipeline where intermediate operations (filter, sort, map, etc.) are applied, and a terminal operation produces the final result.

<code>+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter|->|sorted|->|map|->|collect|
+--------------------+       +------+   +------+   +---+   +-------+</code>

Using the Stream API dramatically improves Java developers' productivity, enabling clean, concise code.

This may be a useful open‑source project: the mall system built with SpringBoot 3 + Vue (GitHub ★ 60K), supporting multi‑module back‑end, Docker, and K8s deployment. Boot project: https://github.com/macrozheng/mall Cloud project: https://github.com/macrozheng/mall-swarm Video tutorials: https://www.macrozheng.com/video/ Project demo:

2. Traversal Operations

2.1 Iterate Collections

Traditional JDK 7 iteration:

<code>/**
 * jdk7 get user ID list
 */
public List<Long> getUserIds(List<User> userList) {
    List<Long> userIds = new ArrayList<>();
    for (User user : userList) {
        userIds.add(user.getUserId());
    }
    return userIds;
}
</code>

With Stream API (JDK 8):

<code>/**
 * jdk8 get user ID list
 */
public List<Long> getUserIds(List<User> userList) {
    List<Long> userIds = userList.stream()
        .map(User::getUserId)
        .collect(Collectors.toList());
    return userIds;
}
</code>

2.2 Filter Elements

JDK 7 filtering null IDs:

<code>/**
 * jdk7 filter non‑null IDs
 */
public List<Long> getUserIds7(List<User> userList) {
    List<Long> userIds = new ArrayList<>();
    for (User user : userList) {
        if (user.getUserId() != null) {
            userIds.add(user.getUserId());
        }
    }
    return userIds;
}
</code>

JDK 8 with

filter

:

<code>/**
 * jdk8 filter non‑null IDs
 */
public List<Long> getUserIds8(List<User> userList) {
    List<Long> userIds = userList.stream()
        .filter(item -> item.getUserId() != null)
        .map(User::getUserId)
        .collect(Collectors.toList());
    return userIds;
}
</code>

2.3 Remove Duplicates

Use

Collectors.toSet()

to deduplicate:

<code>/**
 * jdk8 deduplicate IDs
 */
public Set<Long> getUserIds(List<User> userList) {
    Set<Long> userIds = userList.stream()
        .filter(item -> item.getUserId() != null)
        .map(User::getUserId)
        .collect(Collectors.toSet());
    return userIds;
}
</code>

2.4 Type Conversion

Convert

Long

to

String

:

<code>/**
 * jdk8 convert Long to String
 */
public List<String> getUserIds10(List<Long> userIds) {
    List<String> userIdStrs = userIds.stream()
        .map(x -> x.toString())
        .collect(Collectors.toList());
    return userIdStrs;
}
</code>

2.5 Array to Collection

<code>public static void main(String[] args) {
    // create a string array
    String[] strArray = new String[]{"a", "b", "c"};
    // convert to List
    List<String> strList = Stream.of(strArray).collect(Collectors.toList());
}
</code>

3. Collection to Map Operations

Map a key (e.g., userId) to a value (the whole object) for scenarios where joins are not possible.

3.1 Map without Grouping

JDK 7 manual map:

<code>public Map<Long, User> getMap(List<User> userList) {
    Map<Long, User> userMap = new HashMap<>();
    for (User user : userList) {
        userMap.put(user.getUserId(), user);
    }
    return userMap;
}
</code>

JDK 8 with

Collectors.toMap

(first entry wins on duplicate keys):

<code>public Map<Long, User> getMap(List<User> userList) {
    Map<Long, User> userMap = userList.stream()
        .collect(Collectors.toMap(User::getUserId, v -> v, (k1, k2) -> k1));
    return userMap;
}
</code>

Key points of

Collectors.toMap

:

First argument – key mapper

Second argument – value mapper

Third argument – merge function for duplicate keys

3.2 Map with Grouping

JDK 7 grouping manually:

<code>public Map<Long, List<User>> getMapGroup(List<User> userList) {
    Map<Long, List<User>> userListMap = new HashMap<>();
    for (User user : userList) {
        if (userListMap.containsKey(user.getUserId())) {
            userListMap.get(user.getUserId()).add(user);
        } else {
            List<User> users = new ArrayList<>();
            users.add(user);
            userListMap.put(user.getUserId(), users);
        }
    }
    return userListMap;
}
</code>

JDK 8 one‑liner using

groupingBy

:

<code>public Map<Long, List<User>> getMapGroup(List<User> userList) {
    Map<Long, List<User>> userMap = userList.stream()
        .collect(Collectors.groupingBy(User::getUserId));
    return userMap;
}
</code>

4. Pagination Operations

Stream API can sort and paginate collections:

<code>// data source
List<Integer> numbers = Arrays.asList(3,2,2,3,7,3,5,10,6,20,30,40,50,60,100);
List<Integer> dataList = numbers.stream()
    .sorted((x, y) -> x.compareTo(y))
    .skip(0)   // start index (page offset)
    .limit(10) // page size
    .collect(Collectors.toList());
System.out.println(dataList);
</code>
skip

defines the starting row,

limit

defines the number of rows (page size).

5. Find and Match Operations

Common terminal operations:

allMatch

– checks if all elements satisfy a predicate.

<code>List<Integer> list = Arrays.asList(10,5,7,3);
boolean allMatch = list.stream().allMatch(x -> x > 2);
System.out.println(allMatch);
</code>
findFirst

– returns the first element.

<code>Optional<Integer> first = list.stream().findFirst();
System.out.println(first.get());
</code>
reduce

– combines elements to produce a single value.

<code>Integer result = list.stream().reduce(2, Integer::sum);
System.out.println(result);
</code>

Refer to the official JDK documentation for the full list of operations.

6. Parallel Operations

Parallel streams use multiple CPU cores, while sequential streams run on a single core. Use

parallelStream()

for large data sets on multi‑core servers, but note that parallel may not always be faster for simple tasks.

<code>List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
long count = strings.parallelStream()
    .filter(string -> string.isEmpty())
    .count();
</code>

7. Summary

The article reviews JDK Stream API operations, providing practical code snippets for everyday development needs such as traversal, filtering, deduplication, type conversion, collection‑to‑map conversion, pagination, matching, and parallel processing.

For deeper learning, the author recommends the mall‑swarm microservice project (GitHub ★ 11K) and its 26‑hour video tutorial covering the latest Spring Cloud stack, Kubernetes deployment, and more.

BackendJavaCollectionsJava8Stream API
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.