Mastering flatMap in Java: Real-World Spring Boot 3 Examples
This article explains the powerful flatMap operator in Java streams, covering basic usage, Optional handling, replacing nested loops, API response flattening, combining with filter, reactive programming, and a comprehensive end‑to‑end example, all illustrated with Spring Boot 3 code snippets.
1. Introduction
flatMap is a powerful tool in functional programming that transcends traditional iteration and nested loops. By performing a "map‑then‑flatten" operation, it elegantly handles nested data, collection transformations, and asynchronous streams, enabling clearer and more expressive conversion logic.
2. Practical Cases
2.1 flatMap Basic Usage
Suppose you have a List<List<String>> and want a single list of all strings. Most developers would use flatMap as shown below:
List<List<String>> names = List.of(
List.of("张三", "李四"),
List.of("曹查理", "大卫"),
List.of("女娲", "Jerry")
);
// Flatten the nested list into a single list of strings
List<String> flattened = names.stream()
.flatMap(List::stream)
.toList();
System.out.println(flattened);Output:
[张三, 李四, 曹查理, 大卫, 女娲, Jerry]The result is a Stream<String>, not a Stream<List<String>>.
2.2 flatMap with Optional
When dealing with Optional, nested Optional<Optional<T>> structures can appear. flatMap unwraps the nesting:
public static void main(String[] args) {
Optional<String> nameOpt = Optional.of("Spring Boot3实战案例200讲");
Optional<Optional<String>> result = nameOpt
.map(FlatMapDemo2::toUpperCase); // ❌ nested Optional
System.out.println(result);
}
public static Optional<String> toUpperCase(String s) {
if (s == null || s.isEmpty()) {
return Optional.empty();
}
return Optional.of(s.toLowerCase());
}Output:
Optional[Optional[spring boot3实战案例200讲]]Using flatMap eliminates the nesting:
Optional<String> nameOpt = Optional.of("Spring Boot3实战案例200讲");
Optional<String> result = nameOpt.flatMap(FlatMapDemo2::toUpperCase);
System.err.println(result);Output:
Optional[spring boot3实战案例200讲]2.3 Replacing Nested Loops
flatMap can replace nested loops, such as generating a Cartesian product of products and colors:
List<String> products = List.of("IPad", "IPhone", "Book");
List<String> colors = List.of("黑色", "白色");
List<String> result = products.stream()
.flatMap(product -> colors.stream()
.map(color -> product + " - " + color))
.collect(Collectors.toList());
result.forEach(System.out::println);Output:
IPad - 黑色
IPad - 白色
IPhone - 黑色
IPhone - 白色
Book - 黑色
Book - 白色2.4 flatMap for API Responses
When an API returns lists or optional fields, flatMap can flatten the data without explicit null checks:
public static void main(String[] args) {
List<User> users = List.of(
new User("Pack_xg", Arrays.asList("111", "222")),
new User("pack", Arrays.asList("333")),
new User("xg", Arrays.asList("444", "555"))
);
List<String> result = users.stream()
.flatMap(user -> user.phones().stream())
.toList();
System.out.println(result);
}
public record User(String name, List<String> phones) {}Output:
[111, 222, 333, 444, 555]2.5 flatMap + filter
Combining flatMap with filter can extract valid email addresses from nested lists:
List<List<String>> emails = List.of(
List.of("[email protected]", "www.qq.com"),
List.of("[email protected]"),
List.of("xg@", "[email protected]")
);
List<String> result = emails.stream()
.flatMap(List::stream)
.filter(email -> email.contains("@") && email.contains("."))
.toList();
System.out.println(result);Output:
[[email protected], [email protected], [email protected]]2.6 flatMap in Reactive Programming
Using Project Reactor, flatMap can expand each element into multiple elements in a reactive stream:
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.7.11</version>
</dependency>
Flux<String> users = Flux.just("Pack_xg", "pack", "xg");
users.flatMap(user -> Flux.just(user.toUpperCase(), user.toLowerCase()))
.subscribe(System.out::println);Output:
PACK_XG
pack_xg
PACK
pack
XG
xg2.7 Comprehensive Example
Flatten a hierarchy of users → orders → products to obtain all product names:
// Records
public record Product(String name) {}
public record Order(List<Product> products) {}
public record User(String name, List<Order> orders) {}
// Data preparation
List<Product> p1 = List.of(new Product("iPhone 15"), new Product("AirPods Pro"));
List<Product> p2 = List.of(new Product("MacBook Pro"));
List<Order> o1 = List.of(new Order(p1), new Order(p2));
User zhangsan = new User("张三", o1);
List<Product> lisiProducts1 = List.of(new Product("iPad Air"), new Product("Apple Watch"));
List<Order> o2 = List.of(new Order(lisiProducts1));
User lisi = new User("李四", o2);
User wangwu = new User("王五", List.of());
List<User> users = List.of(zhangsan, lisi, wangwu);
// Using flatMap
List<String> productNames = users.stream()
.flatMap(user -> user.orders().stream())
.flatMap(order -> order.products().stream())
.map(Product::name)
.toList();
System.out.println(productNames);Output:
[iPhone 15, AirPods Pro, MacBook Pro, iPad Air, Apple Watch]Mastering flatMap not only simplifies code but also changes the way you think about data transformation, eliminating repetitive nested loops.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
