1. Overview
Both map() and flatMap() are intermediate operations in the Java Stream API, but they serve different purposes when transforming stream elements.
Key Differences
| Aspect | map() | flatMap() |
|---|---|---|
| Purpose | One-to-one transformation | One-to-many transformation |
| Return Type | Stream<R> | Stream<R> |
| Input | Single element → Single element | Single element → Multiple elements |
| Output Structure | Preserves stream structure | Flattens nested structures |
| Use Case | Simple transformations | Flattening collections of collections |
2. map() Operation
Basic Concept
- One-to-one transformation
- Each input element produces exactly one output element
- Preserves the stream size (1:1 mapping)
Syntax
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
Basic Examples
import java.util.*;
import java.util.stream.*;
public class MapExamples {
public static void main(String[] args) {
List<String> names = Arrays.asList("john", "jane", "alice", "bob");
// Example 1: Convert to uppercase
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Uppercase: " + upperCaseNames);
// Example 2: Get string lengths
List<Integer> nameLengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("Lengths: " + nameLengths);
// Example 3: Transform objects
List<Person> people = Arrays.asList(
new Person("John", 25),
new Person("Jane", 30),
new Person("Alice", 28)
);
List<String> personNames = people.stream()
.map(Person::getName)
.collect(Collectors.toList());
System.out.println("Names only: " + personNames);
List<Integer> agesPlusFive = people.stream()
.map(person -> person.getAge() + 5)
.collect(Collectors.toList());
System.out.println("Ages + 5: " + agesPlusFive);
}
static class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
}
Advanced Map Examples
import java.util.*;
import java.util.stream.*;
public class AdvancedMapExamples {
public static void main(String[] args) {
// Example 1: Mathematical transformations
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> squares = numbers.stream()
.map(n -> n * n)
.collect(Collectors.toList());
System.out.println("Squares: " + squares);
List<Double> squareRoots = numbers.stream()
.map(Math::sqrt)
.collect(Collectors.toList());
System.out.println("Square roots: " + squareRoots);
// Example 2: String transformations with conditions
List<String> words = Arrays.asList("hello", "world", "java", "streams");
List<String> transformed = words.stream()
.map(word -> {
if (word.length() > 5) {
return word.toUpperCase();
} else {
return word.toLowerCase();
}
})
.collect(Collectors.toList());
System.out.println("Conditional transform: " + transformed);
// Example 3: Extracting and transforming nested properties
List<Order> orders = Arrays.asList(
new Order("ORD001", 150.0),
new Order("ORD002", 200.0),
new Order("ORD003", 75.0)
);
List<String> orderIds = orders.stream()
.map(Order::getId)
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("Order IDs: " + orderIds);
// Example 4: Chaining multiple map operations
List<Double> discountedPrices = orders.stream()
.map(Order::getAmount)
.map(amount -> amount * 0.9) // 10% discount
.map(amount -> Math.round(amount * 100.0) / 100.0) // round to 2 decimal places
.collect(Collectors.toList());
System.out.println("Discounted prices: " + discountedPrices);
}
static class Order {
private String id;
private double amount;
public Order(String id, double amount) {
this.id = id;
this.amount = amount;
}
public String getId() { return id; }
public double getAmount() { return amount; }
}
}
3. flatMap() Operation
Basic Concept
- One-to-many transformation
- Each input element can produce zero, one, or multiple output elements
- Flattens nested structures into a single stream
Syntax
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
Basic Examples
import java.util.*;
import java.util.stream.*;
public class FlatMapBasicExamples {
public static void main(String[] args) {
// Example 1: Flattening lists of lists
List<List<String>> listOfLists = Arrays.asList(
Arrays.asList("a", "b", "c"),
Arrays.asList("d", "e", "f"),
Arrays.asList("g", "h", "i")
);
List<String> flattened = listOfLists.stream()
.flatMap(List::stream) // Convert each list to stream and flatten
.collect(Collectors.toList());
System.out.println("Flattened list: " + flattened);
// Example 2: Splitting strings into characters
List<String> words = Arrays.asList("hello", "world");
List<String> characters = words.stream()
.flatMap(word -> Arrays.stream(word.split("")))
.collect(Collectors.toList());
System.out.println("Characters: " + characters);
// Example 3: Handling optional values
List<Optional<String>> optionalList = Arrays.asList(
Optional.of("value1"),
Optional.empty(),
Optional.of("value2"),
Optional.of("value3")
);
List<String> presentValues = optionalList.stream()
.flatMap(Optional::stream) // Only present values
.collect(Collectors.toList());
System.out.println("Present values: " + presentValues);
// Example 4: Combining multiple streams
List<String> list1 = Arrays.asList("A", "B", "C");
List<String> list2 = Arrays.asList("D", "E", "F");
List<String> combined = Stream.of(list1, list2)
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("Combined: " + combined);
}
}
Advanced FlatMap Examples
import java.util.*;
import java.util.stream.*;
public class AdvancedFlatMapExamples {
public static void main(String[] args) {
// Example 1: Processing nested objects
List<Department> departments = Arrays.asList(
new Department("Engineering", Arrays.asList(
new Employee("John", "Doe", 5000),
new Employee("Jane", "Smith", 6000)
)),
new Department("Marketing", Arrays.asList(
new Employee("Alice", "Johnson", 4500),
new Employee("Bob", "Brown", 5500)
)),
new Department("Sales", Collections.emptyList()) // Empty department
);
// Get all employees from all departments
List<Employee> allEmployees = departments.stream()
.flatMap(dept -> dept.getEmployees().stream())
.collect(Collectors.toList());
System.out.println("All employees: " + allEmployees.size());
allEmployees.forEach(emp ->
System.out.println(emp.getFirstName() + " " + emp.getLastName()));
// Example 2: Flattening and transforming
List<String> allFirstNames = departments.stream()
.flatMap(dept -> dept.getEmployees().stream())
.map(Employee::getFirstName)
.collect(Collectors.toList());
System.out.println("All first names: " + allFirstNames);
// Example 3: Complex string processing
List<String> sentences = Arrays.asList(
"Hello world",
"Java streams are powerful",
"FlatMap vs Map"
);
List<String> uniqueWords = sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.map(String::toLowerCase)
.distinct()
.collect(Collectors.toList());
System.out.println("Unique words: " + uniqueWords);
// Example 4: Matrix flattening
Integer[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
List<Integer> flattenedMatrix = Arrays.stream(matrix)
.flatMap(Arrays::stream)
.collect(Collectors.toList());
System.out.println("Flattened matrix: " + flattenedMatrix);
// Example 5: Filtering while flattening
List<List<Integer>> numberLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9)
);
List<Integer> evenNumbers = numberLists.stream()
.flatMap(List::stream)
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
System.out.println("Even numbers: " + evenNumbers);
}
static class Department {
private String name;
private List<Employee> employees;
public Department(String name, List<Employee> employees) {
this.name = name;
this.employees = employees;
}
public List<Employee> getEmployees() { return employees; }
public String getName() { return name; }
}
static class Employee {
private String firstName;
private String lastName;
private double salary;
public Employee(String firstName, String lastName, double salary) {
this.firstName = firstName;
this.lastName = lastName;
this.salary = salary;
}
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public double getSalary() { return salary; }
@Override
public String toString() {
return firstName + " " + lastName;
}
}
}
4. Side-by-Side Comparison
Direct Comparison Examples
import java.util.*;
import java.util.stream.*;
public class MapVsFlatMapComparison {
public static void main(String[] args) {
System.out.println("=== MAP vs FLATMAP COMPARISON ===\n");
List<String> words = Arrays.asList("hello", "world", "java");
// Example 1: String processing
System.out.println("1. STRING PROCESSING:");
// map() - transforms each string to its length
List<Integer> wordLengths = words.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println("map() - word lengths: " + wordLengths);
// flatMap() - splits each string into characters
List<String> characters = words.stream()
.flatMap(word -> Arrays.stream(word.split("")))
.collect(Collectors.toList());
System.out.println("flatMap() - characters: " + characters);
// Example 2: Working with nested collections
System.out.println("\n2. NESTED COLLECTIONS:");
List<List<Integer>> numberLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6)
);
// map() - preserves the structure
List<List<Integer>> mapped = numberLists.stream()
.map(list -> list.stream()
.map(n -> n * 2)
.collect(Collectors.toList()))
.collect(Collectors.toList());
System.out.println("map() - doubled nested: " + mapped);
// flatMap() - flattens the structure
List<Integer> flattened = numberLists.stream()
.flatMap(List::stream)
.map(n -> n * 2)
.collect(Collectors.toList());
System.out.println("flatMap() - doubled flat: " + flattened);
// Example 3: Optional handling
System.out.println("\n3. OPTIONAL HANDLING:");
List<Optional<String>> optionalValues = Arrays.asList(
Optional.of("value1"),
Optional.empty(),
Optional.of("value2")
);
// map() - preserves Optional wrapper
List<Optional<String>> mappedOptionals = optionalValues.stream()
.map(opt -> opt.map(String::toUpperCase))
.collect(Collectors.toList());
System.out.println("map() - with Optionals: " + mappedOptionals);
// flatMap() - removes empty Optionals and unwraps values
List<String> flatMappedValues = optionalValues.stream()
.flatMap(Optional::stream)
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println("flatMap() - values only: " + flatMappedValues);
// Example 4: Stream of streams
System.out.println("\n4. STREAM OF STREAMS:");
// map() - produces Stream<Stream<Integer>>
Stream<Stream<Integer>> mapResult = numberLists.stream()
.map(List::stream);
System.out.println("map() result type: Stream<Stream<Integer>>");
// flatMap() - produces Stream<Integer>
Stream<Integer> flatMapResult = numberLists.stream()
.flatMap(List::stream);
System.out.println("flatMap() result type: Stream<Integer>");
}
}
5. Real-World Use Cases
Data Processing Examples
import java.util.*;
import java.util.stream.*;
public class RealWorldExamples {
public static void main(String[] args) {
// Use Case 1: E-commerce order processing
processOrders();
// Use Case 2: Social media analytics
analyzeSocialMedia();
// Use Case 3: File processing
processFiles();
}
static void processOrders() {
System.out.println("=== E-COMMERCE ORDER PROCESSING ===");
List<CustomerOrder> orders = Arrays.asList(
new CustomerOrder("CUST001", Arrays.asList(
new OrderItem("Laptop", 1, 999.99),
new OrderItem("Mouse", 2, 29.99)
)),
new CustomerOrder("CUST002", Arrays.asList(
new OrderItem("Keyboard", 1, 79.99),
new OrderItem("Monitor", 1, 299.99),
new OrderItem("USB Cable", 3, 9.99)
)),
new CustomerOrder("CUST003", Collections.emptyList()) // Empty order
);
// Get all order items using flatMap
List<OrderItem> allItems = orders.stream()
.flatMap(order -> order.getItems().stream())
.collect(Collectors.toList());
System.out.println("All items count: " + allItems.size());
// Calculate total revenue
double totalRevenue = orders.stream()
.flatMap(order -> order.getItems().stream())
.mapToDouble(OrderItem::getTotalPrice)
.sum();
System.out.println("Total revenue: $" + totalRevenue);
// Get all product names
List<String> allProducts = orders.stream()
.flatMap(order -> order.getItems().stream())
.map(OrderItem::getProductName)
.distinct()
.collect(Collectors.toList());
System.out.println("All products: " + allProducts);
}
static void analyzeSocialMedia() {
System.out.println("\n=== SOCIAL MEDIA ANALYTICS ===");
List<User> users = Arrays.asList(
new User("user1", Arrays.asList("java", "programming", "coding")),
new User("user2", Arrays.asList("java", "spring", "hibernate")),
new User("user3", Arrays.asList("python", "machine learning")),
new User("user4", Collections.emptyList()) // No interests
);
// Find all unique interests
List<String> allInterests = users.stream()
.flatMap(user -> user.getInterests().stream())
.distinct()
.collect(Collectors.toList());
System.out.println("All interests: " + allInterests);
// Count interest frequency
Map<String, Long> interestFrequency = users.stream()
.flatMap(user -> user.getInterests().stream())
.collect(Collectors.groupingBy(
interest -> interest,
Collectors.counting()
));
System.out.println("Interest frequency: " + interestFrequency);
// Find users interested in Java
List<String> javaUsers = users.stream()
.filter(user -> user.getInterests().contains("java"))
.map(User::getUsername)
.collect(Collectors.toList());
System.out.println("Java users: " + javaUsers);
}
static void processFiles() {
System.out.println("\n=== FILE PROCESSING ===");
List<TextFile> files = Arrays.asList(
new TextFile("document1.txt", Arrays.asList(
"Hello world this is line one",
"Java streams are powerful tools",
"FlatMap helps with nested structures"
)),
new TextFile("document2.txt", Arrays.asList(
"Another document with important data",
"Processing files with streams is efficient"
))
);
// Get all lines from all files
List<String> allLines = files.stream()
.flatMap(file -> file.getLines().stream())
.collect(Collectors.toList());
System.out.println("Total lines: " + allLines.size());
// Get all unique words
List<String> allWords = files.stream()
.flatMap(file -> file.getLines().stream())
.flatMap(line -> Arrays.stream(line.split(" ")))
.map(String::toLowerCase)
.distinct()
.collect(Collectors.toList());
System.out.println("Unique words count: " + allWords.size());
// Find most common words
Map<String, Long> wordFrequency = files.stream()
.flatMap(file -> file.getLines().stream())
.flatMap(line -> Arrays.stream(line.split(" ")))
.map(String::toLowerCase)
.collect(Collectors.groupingBy(
word -> word,
Collectors.counting()
));
wordFrequency.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(5)
.forEach(entry ->
System.out.println(entry.getKey() + ": " + entry.getValue()));
}
static class CustomerOrder {
private String customerId;
private List<OrderItem> items;
public CustomerOrder(String customerId, List<OrderItem> items) {
this.customerId = customerId;
this.items = items;
}
public List<OrderItem> getItems() { return items; }
public String getCustomerId() { return customerId; }
}
static class OrderItem {
private String productName;
private int quantity;
private double unitPrice;
public OrderItem(String productName, int quantity, double unitPrice) {
this.productName = productName;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
public String getProductName() { return productName; }
public double getTotalPrice() { return quantity * unitPrice; }
}
static class User {
private String username;
private List<String> interests;
public User(String username, List<String> interests) {
this.username = username;
this.interests = interests;
}
public String getUsername() { return username; }
public List<String> getInterests() { return interests; }
}
static class TextFile {
private String filename;
private List<String> lines;
public TextFile(String filename, List<String> lines) {
this.filename = filename;
this.lines = lines;
}
public List<String> getLines() { return lines; }
public String getFilename() { return filename; }
}
}
6. Common Patterns and Best Practices
Pattern 1: Chaining map and flatMap
import java.util.*;
import java.util.stream.*;
public class ChainingPatterns {
public static void main(String[] args) {
List<String> sentences = Arrays.asList(
"Java is great",
"Streams are powerful",
"FlatMap transforms data"
);
// Pattern: flatMap → map → filter
List<String> longWords = sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" "))) // Flatten
.map(String::toLowerCase) // Transform
.filter(word -> word.length() > 5) // Filter
.distinct() // Deduplicate
.collect(Collectors.toList());
System.out.println("Long words: " + longWords);
// Pattern: map → flatMap for nested transformations
List<List<Integer>> numberLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6)
);
List<Integer> processedNumbers = numberLists.stream()
.map(list -> list.stream() // Transform each inner list
.map(n -> n * 2) // Double each number
.collect(Collectors.toList())) // Back to list
.flatMap(List::stream) // Then flatten
.collect(Collectors.toList());
System.out.println("Processed numbers: " + processedNumbers);
}
}
Pattern 2: Handling nulls and empty collections
import java.util.*;
import java.util.stream.*;
public class NullSafetyPatterns {
public static void main(String[] args) {
List<List<String>> potentiallyNullLists = Arrays.asList(
Arrays.asList("a", "b"),
null,
Arrays.asList("c", "d"),
Collections.emptyList(),
null
);
// Safe flatMap with null checks
List<String> safeFlattened = potentiallyNullLists.stream()
.filter(Objects::nonNull) // Filter out null lists
.flatMap(List::stream) // Flatten non-null lists
.filter(Objects::nonNull) // Filter out null elements
.collect(Collectors.toList());
System.out.println("Safe flattened: " + safeFlattened);
// Using Optional for safer operations
List<String> optionalSafe = potentiallyNullLists.stream()
.map(list -> Optional.ofNullable(list).orElse(Collections.emptyList()))
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("Optional safe: " + optionalSafe);
}
}
7. Performance Considerations
import java.util.*;
import java.util.stream.*;
import java.util.concurrent.TimeUnit;
public class PerformanceComparison {
public static void main(String[] args) {
List<List<Integer>> largeDataSet = createLargeDataSet();
// Test map performance
long mapStartTime = System.nanoTime();
List<List<Integer>> mapResult = largeDataSet.stream()
.map(list -> list.stream()
.map(n -> n * 2)
.collect(Collectors.toList()))
.collect(Collectors.toList());
long mapEndTime = System.nanoTime();
// Test flatMap performance
long flatMapStartTime = System.nanoTime();
List<Integer> flatMapResult = largeDataSet.stream()
.flatMap(List::stream)
.map(n -> n * 2)
.collect(Collectors.toList());
long flatMapEndTime = System.nanoTime();
System.out.printf("Map time: %,d ns%n", mapEndTime - mapStartTime);
System.out.printf("FlatMap time: %,d ns%n", flatMapEndTime - flatMapStartTime);
System.out.printf("Map result size: %,d%n",
mapResult.stream().mapToInt(List::size).sum());
System.out.printf("FlatMap result size: %,d%n", flatMapResult.size());
}
private static List<List<Integer>> createLargeDataSet() {
List<List<Integer>> data = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 1000; i++) {
List<Integer> innerList = new ArrayList<>();
for (int j = 0; j < 100; j++) {
innerList.add(random.nextInt(1000));
}
data.add(innerList);
}
return data;
}
}
Summary
When to Use Map:
- One-to-one element transformations
- Simple property extraction
- Type conversions
- Mathematical operations on elements
When to Use FlatMap:
- One-to-many element transformations
- Flattening nested collections
- Combining multiple streams into one
- Filtering out empty Optionals
- Processing hierarchical data structures
Key Takeaways:
- map() preserves structure, flatMap() flattens structure
- map() is for transformation, flatMap() is for transformation + flattening
- Use map() when you want to work with individual elements in their context
- Use flatMap() when you want to combine or flatten nested streams
- Both can be chained together for complex data processing pipelines