Java Stream API Deep Dive
Master the Stream API for functional programming in Java

Java Stream API Deep Dive: Practical Examples for efficient coding
Introduction to Java Stream API
The Stream API, introduced in Java 8, revolutionized how we process collections by providing a declarative, functional, and parallel-processing approach. Instead of writing verbose loops, developers can chain operations like filtering, mapping, and reducing with clean, readable, and expressive syntax.
Key Advantages:
✔ Concise & Readable – Eliminates boilerplate loops
✔ Lazy Evaluation – Optimizes performance by processing only when needed
✔ Parallel Execution – Enables multi-threaded processing with parallelStream()
✔ Functional Style – Encourages immutability and reduces side effects
In this article, we'll explore practical use cases of the Stream API with real-world examples.
1. Frequency Map: Count Occurrences in a List
Use Case: Analysing word frequencies, log analysis.
List<String> items = List.of("apple", "banana", "apple");
Map<String, Long> itemCounts = items.stream()
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
));
// Output: {banana=1, apple=2}2. Finding Duplicates in an Array
Use Case: Data validation, deduplication.
int[] array = {1, 2, 3, 2, 4, 5, 3, 6, 7};
List<Integer> duplicates = Arrays.stream(array)
.boxed()
.collect(Collectors.groupingBy(
Function.identity(),
Collectors.counting()
))
.entrySet().stream()
.filter(entry -> entry.getValue() > 1)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// Output: [2, 3]3. Grouping & Aggregating Data
3.1 Filtering Within Groups
Use Case: Categorizing high-calorie dishes by type.
Map<Dish.Type, List<Dish>> highCalorieDishesByType = menu.stream()
.collect(Collectors.groupingBy(
Dish::getType,
Collectors.filtering(
dish -> dish.getCalories() > 500,
Collectors.toList()
)
));3.2 Nested Grouping with Mapping
Use Case: Extracting dish names grouped by type.
Map<Dish.Type, List<String>> groupedDishNames = menu.stream()
.collect(Collectors.groupingBy(
Dish::getType,
Collectors.mapping(Dish::getName, Collectors.toList())
));3.3 Top 3 Highest items per category
Use Case: Leaderboards, recommendations.
Map<Dish.Type, List<Dish>> topDishesByType = menu.stream()
.collect(Collectors.groupingBy(
Dish::getType,
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.sorted(Comparator.comparingInt(Dish::getCalories).reversed())
.limit(3)
.collect(Collectors.toList())
)
));4. Partitioning Numbers into Even & Odd
Use Case: Data segmentation for separate processing.
List<Integer> numbers = IntStream.rangeClosed(1, 10).boxed().toList();
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// Output: {false=[1, 3, 5, 7, 9], true=[2, 4, 6, 8, 10]}5. Selective Filtering: takeWhile & dropWhile
Use Case: Processing sorted data (e.g., logs, time-series).
Advantages: Short-circuiting. It takes elements from the start to the first mismatch.
List<Dish> lowCalorie = menu.stream()
.takeWhile(dish -> dish.getCalories() < 320)
.collect(Collectors.toList());
List<Dish> highCalorie = menu.stream()
.dropWhile(dish -> dish.getCalories() < 320)
.collect(Collectors.toList());6. Detecting Duplicates in a Collection
Use Case: Preventing duplicate entries in APIs/databases.
List<Integer> list = List.of(1, 2, 3, 4, 5, 3, 2, 6);
Set<Integer> seen = new HashSet<>();
Set<Integer> duplicates = list.stream()
.filter(n -> !seen.add(n))
.collect(Collectors.toSet());
// Output: [2, 3]7. Custom Collector with Collector.of
Use Case: Custom string joining (e.g., CSV, logs).
Collector<String, StringJoiner, String> customJoiner = Collector.of(
() -> new StringJoiner(" | "),
StringJoiner::add,
StringJoiner::merge,
StringJoiner::toString
);
String joined = Stream.of("apple", "banana", "cherry").collect(customJoiner);
// Output: "apple | banana | cherry"8. Top-N by Group (e.g., Top 3 Employees per Department)
Use Case: Dashboards, leaderboards.
Map<String, List<Employee>> topEmployees = employees.stream()
.collect(Collectors.groupingBy(
Employee::getDepartment,
Collectors.collectingAndThen(
Collectors.toList(),
list -> list.stream()
.sorted(Comparator.comparingDouble(Employee::getPerformanceScore).reversed())
.limit(3)
.collect(Collectors.toList())
)
));9. Find First Non-Repeating Character in a String
Use Case: Data validation, text processing.
String input = "swiss";
Character firstUnique = input.chars()
.mapToObj(c -> (char) c)
.collect(Collectors.groupingBy(
c -> c,
LinkedHashMap::new,
Collectors.counting()
))
.entrySet().stream()
.filter(e -> e.getValue() == 1)
.map(Map.Entry::getKey)
.findFirst()
.orElse(null);
// Output: 'w'10. Generate Cartesian Product
Use Case: Combining all pairs (e.g., users × roles).
List<String> users = List.of("Alice", "Bob");
List<String> roles = List.of("Admin", "User");
List<String> pairs = users.stream()
.flatMap(user -> roles.stream().map(role -> user + "-" + role))
.collect(Collectors.toList());
// Output: [Alice-Admin, Alice-User, Bob-Admin, Bob-User]11. Advanced Use Cases
11.1 Compute Median from a Dynamic Dataset
List<Integer> nums = List.of(3, 5, 1, 4, 2);
double median = nums.stream()
.sorted()
.skip((nums.size() - 1) / 2)
.limit(nums.size() % 2 == 0 ? 2 : 1)
.mapToInt(Integer::intValue)
.average()
.orElse(0);
// Output: 3.011.2 Paginate Large Lists
int page = 2, pageSize = 5;
List<String> paged = items.stream()
.skip((page - 1) * pageSize)
.limit(pageSize)
.collect(Collectors.toList());12. Debugging Stream Pipelines with peek()
Use Case: Logging intermediate steps.
List<String> results = Stream.of("stream", "api", "power", "rocks")
.peek(s -> System.out.println("Original: " + s))
.map(String::toUpperCase)
.peek(s -> System.out.println("Uppercased: " + s))
.filter(s -> s.length() > 4)
.collect(Collectors.toList());13. Zipping Two Lists
Use Case: Merging parallel data (e.g., names + scores).
List<String> names = List.of("Alice", "Bob");
List<Integer> scores = List.of(85, 92);
List<String> zipped = IntStream.range(0, Math.min(names.size(), scores.size()))
.mapToObj(i -> names.get(i) + ":" + scores.get(i))
.collect(Collectors.toList());
// Output: [Alice:85, Bob:92]Best Practices
✔ Prefer Method References
✔ Use Parallel Streams Wisely (Only for large datasets)
✔ Replace peek() with Logging in Production
Conclusion
The Stream API is a powerful tool for processing collections efficiently and elegantly. Mastering these techniques allows writing cleaner, more maintainable, and often faster Java code.
Did you find this article useful?