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