Question

Flatten a List of Lists into a List in Java 8

javacollectionsjava-8

Question

Given a List<List<Object>>, how can you create a single List<Object> containing every object from the nested lists in the same encounter order by using Java 8 features?

Short Answer

You will learn how Java 8 streams transform nested collections into one flat collection with flatMap, while preserving the order of elements. You will also see alternatives, null-handling options, and common pitfalls.

Concept

A List<List<Object>> is a nested collection: its outer list contains inner lists rather than individual objects.

To create one list containing the objects from every inner list, you need to flatten the structure. In Java 8 Streams, flatMap is designed for this job.

  • map transforms each input item into exactly one output item.
  • flatMap transforms each input item into a stream of output items, then joins those streams into one stream.

For nested lists, each inner List<Object> can become a Stream<Object>. flatMap combines those streams, and collect(Collectors.toList()) stores the resulting objects in a new list.

This matters because nested data appears often in real programs: orders containing line items, teams containing members, API responses containing grouped results, and batches containing records.

Mental Model

Imagine the outer list as a stack of trays. Each tray is an inner list, and each tray holds objects.

  • map would replace every tray with something else, but you would still have one result per tray.
  • flatMap removes the tray boundaries and pours all objects onto one long conveyor belt.

The conveyor belt is the final stream of objects, which you collect into a single List<Object>.

Take Quiz

Syntax and Examples

The usual Java 8 pattern is:

List<T> flattened = nestedLists.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());

Example:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<List<String>> wordsByGroup = Arrays.asList(
                Arrays.asList("red", "blue"),
                Arrays.asList("green"),
                Arrays.asList("yellow", "purple")
        );

        List<String> words = wordsByGroup.stream()
                .flatMap(List::stream)
                .collect(Collectors.toList());

        System.out.println(words);
    }
}

Output:

[red, blue, green, yellow, purple]

wordsByGroup.stream() produces a stream of inner lists. flatMap(List::stream) turns each inner list into a stream of strings and merges them into one stream. Finally, collect(...) creates the new List<String>.

Step by Step Execution

Consider this code:

List<List<Integer>> batches = Arrays.asList(
        Arrays.asList(10, 20),
        Arrays.asList(30),
        Arrays.asList(40, 50)
);

List<Integer> values = batches.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());

Execution trace:

  1. batches.stream() visits the outer list in order.
  2. The first inner list is [10, 20]. List::stream produces 10, then 20.
  3. The second inner list is [30]. It produces 30.
  4. The third inner list is [40, 50]. It produces 40, then 50.
  5. flatMap combines those element streams into one sequence: 10, 20, 30, 40, 50.
  6. collect(Collectors.toList()) creates values as [10, 20, 30, 40, 50].

With ordinary ordered lists and a sequential stream, encounter order is preserved.

Real World Use Cases

Common flattening scenarios include:

  • E-commerce: Convert List<Order> into one list of all order line items.
  • User permissions: Combine permission lists from several roles before checking access.
  • API processing: Flatten paginated or grouped API results into one result list.
  • File processing: Combine lists of parsed records from multiple input files.
  • Validation: Collect all validation errors from several form sections.
  • Data import: Flatten batches of database rows before filtering or transforming them.
Take Quiz

Real Codebase Usage

In production code, flattening is often followed by another stream operation.

Flatten and filter

List<String> activeUsernames = usersByTeam.stream()
        .flatMap(List::stream)
        .filter(User::isActive)
        .map(User::getUsername)
        .collect(Collectors.toList());

Flatten and remove duplicates

List<String> uniqueTags = tagsByArticle.stream()
        .flatMap(List::stream)
        .distinct()
        .collect(Collectors.toList());

Ignore null inner lists

If an outer list might contain null instead of an inner list, filter those values before calling stream():

List<String> names = groups.stream()
        .filter(Objects::nonNull)
        .flatMap(List::stream)
        .collect(Collectors.toList());

A common design preference is to avoid null collections altogether. Return an empty list when there are no items, because an empty list works naturally with flatMap.

Common Mistakes

Using map instead of flatMap

This code does not flatten the lists:

List<Stream<Object>> streams = nestedLists.stream()
        .map(List::stream)
        .collect(Collectors.toList());

map(List::stream) creates one Stream<Object> per inner list. The result still has one item per inner list. Use flatMap(List::stream) to merge those streams.

Forgetting to collect the stream

nestedLists.stream().flatMap(List::stream);

Streams are lazy. Without a terminal operation such as collect, forEach, or count, no useful final result is produced.

Calling stream() on a null inner list

nestedLists.stream()
        .flatMap(List::stream) // Fails if an inner list is null
        .collect(Collectors.toList());

Filter null inner lists first, or ensure your program uses empty lists instead of null.

Assuming any collection preserves order

A List preserves insertion order, so flattening a List<List<T>> sequentially gives predictable order. A Set, especially a HashSet, may not have a predictable iteration order.

Expecting a mutable list in every situation

Collectors.toList() commonly returns a mutable list, but its exact list implementation is not guaranteed. If you specifically need an ArrayList, use:

.collect(Collectors.toCollection(ArrayList::new));

Comparisons

ApproachResultWhen to use it
flatMap(List::stream)One stream containing all nested elementsStandard Java 8 flattening approach
map(List::stream)A stream of streamsUse only when you intentionally need separate streams
Nested for loopsOne list after manually adding itemsUseful when stream code would be less readable or logic is complex
addAll in a loopOne list after copying each inner listA simple non-stream alternative

A loop-based alternative is also valid:

List<Object> result = new ArrayList<>();
for (List<Object> innerList : nestedLists) {
    result.addAll(innerList);
}

For a direct flattening operation, the stream version is concise. For complicated branching, logging, checked exceptions, or mutation, a loop can be easier to read and debug.

Take Quiz

Cheat Sheet

// Flatten a List<List<T>> into List<T>
List<T> result = nested.stream()
        .flatMap(List::stream)
        .collect(Collectors.toList());
// Equivalent lambda form
.flatMap(innerList -> innerList.stream())
// Safely ignore null inner lists
nested.stream()
        .filter(Objects::nonNull)
        .flatMap(List::stream)
        .collect(Collectors.toList());

Key rules:

  • Use flatMap to remove one level of nesting.
  • Use map when each input should produce one output.
  • collect(Collectors.toList()) turns a stream into a list.
  • Sequential streams over ordered lists preserve encounter order.
  • Prefer empty lists over null lists when possible.

FAQ

What does flatMap(List::stream) mean in Java?

It converts each inner list into a stream and merges all of those element streams into one stream.

Does flatMap preserve the order of a List<List<T>>?

Yes, for a sequential stream over normal ordered lists. Elements are visited in outer-list order and then inner-list order.

Can I use a lambda instead of List::stream?

Yes. These are equivalent:

.flatMap(List::stream)
.flatMap(list -> list.stream())

What happens if an inner list is empty?

It contributes no elements. flatMap simply continues with the next inner list.

What happens if an inner list is null?

Calling stream() on it throws a NullPointerException. Filter null values or use empty lists instead.

Can I flatten a list of lists into a set?

Yes. Replace toList() with toSet() when you want unique elements and do not require list behavior:

.collect(Collectors.toSet())

Does flatMap modify the original lists?

No. The stream pipeline reads from the source lists and creates a separate collected result.

Take Quiz

Mini Project

Description

Build a small order-item report. Each order contains a list of purchased product names. Flatten all orders into one list, remove duplicate product names, and print the result. This mirrors reporting and inventory tasks that combine nested records.

Goal

Create one alphabetically sorted list of unique products from nested order data using Java 8 streams.

Requirements

  • Create a List<List<String>> containing at least three orders.
  • Flatten all product lists into one stream.
  • Ignore any empty order lists naturally.
  • Remove duplicate product names.
  • Sort the final product list alphabetically.
  • Print the final list.
Take Quiz

Keep learning

Related questions

View more