Overview of groupingBy in Java 8 Streams
The groupingBy collector in Java 8 is part of the Collectors class and is used to group elements in a stream by a specified classifier function. It works similarly to the SQL GROUP BY statement, where data is categorized based on certain attributes. The result is typically a Map, where the keys are the grouping criteria, and the values are the grouped elements.
Steps to Use groupingBy
Prepare Your Data: Have a Collection or Stream of elements (e.g., a List of objects).
Stream Your Data: Convert your collection into a stream using .stream().
Group Elements:
Use Collectors.groupingBy() to specify how the elements should be grouped.
Optionally, apply downstream collectors for further aggregation (e.g., counting(), mapping()).
Examples 1 : Group by String length 
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class GroupingByExample {
    public static void main(String[] args) {
        // Sample data
        List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "fig", "grape");
        // Group words by their length
        Map<Integer, List<String>> groupedByLength = words.stream()
                .collect(Collectors.groupingBy(String::length));
        // Output the result
        System.out.println("Grouped by Length: " + groupedByLength);
    }
}
Output:
Grouped by Length: {3=[fig], 4=[date], 5=[apple, grape], 6=[banana, cherry]}
Explanation:
String::length is the classifier function that groups the words by their length.
The result is a Map where the key is the word length, and the value is a list of words with that length.
Exmaple 2: Group employee by department 
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Employee {
    String name;
    String department;
    Employee(String name, String department) {
        this.name = name;
        this.department = department;
    }
    @Override
    public String toString() {
        return name;
    }
}
public class GroupingByExample {
    public static void main(String[] args) {
        // Sample data
        List<Employee> employees = Arrays.asList(
                new Employee("Alice", "HR"),
                new Employee("Bob", "IT"),
                new Employee("Charlie", "HR"),
                new Employee("David", "Finance"),
                new Employee("Eve", "IT")
        );
        // Group employees by department
        Map<String, List<Employee>> groupedByDepartment = employees.stream()
                .collect(Collectors.groupingBy(employee -> employee.department));
        // Output the result
        System.out.println("Grouped by Department: " + groupedByDepartment);
    }
}
output:
Grouped by Department: {Finance=[David], HR=[Alice, Charlie], IT=[Bob, Eve]}
Explanation:
The classifier function is employee -> employee.department, which groups employees by their department.
The result is a Map where the key is the department, and the value is a list of employees in that department.
Example 3: Group and Count Elements
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class GroupingByCounting {
    public static void main(String[] args) {
        // Sample data
        List<String> items = Arrays.asList("apple", "banana", "apple", "orange", "banana", "apple");
        // Group and count occurrences
        Map<String, Long> itemCounts = items.stream()
                .collect(Collectors.groupingBy(item -> item, Collectors.counting()));
        // Output the result
        System.out.println("Item Counts: " + itemCounts);
    }
}
Output:
Item Counts: {orange=1, banana=2, apple=3}
Explanation:
The classifier function is item -> item, grouping elements by their value.
The downstream collector Collectors.counting() counts the occurrences of each group.
Key Features of groupingBy
Basic Grouping:
Groups elements based on a key or classifier.
E.g., Collectors.groupingBy(Function.identity()).
Downstream Collectors:
Apply further operations like counting, mapping, or reducing on the grouped elements.
Example: Collectors.groupingBy(key, Collectors.counting()).
Nested Grouping:
Group by multiple levels using nested groupingBy.
Example: Group employees by department and then by job title.
Custom Map Implementation:
Use the three-argument version of groupingBy to specify the type of Map to use for the result.
Nested grouping with groupingBy in Java 8 allows you to group elements by multiple levels. This involves creating a Map where each key corresponds to a group, and the value is another map representing the next level of grouping. Here’s how to achieve this with some examples:
Example 1: Group Employees by Department and then by Designation
java
Pimport java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Employee {
    String name;
    String department;
    String designation;
    Employee(String name, String department, String designation) {
        this.name = name;
        this.department = department;
        this.designation = designation;
    }
    @Override
    public String toString() {
        return name;
    }
}
public class NestedGroupingExample {
    public static void main(String[] args) {
        // Sample data
        List<Employee> employees = Arrays.asList(
            new Employee("Alice", "HR", "Manager"),
            new Employee("Bob", "IT", "Developer"),
            new Employee("Charlie", "HR", "Executive"),
            new Employee("David", "IT", "Developer"),
            new Employee("Eve", "Finance", "Analyst")
        );
        // Nested grouping by department and then by designation
        Map<String, Map<String, List<Employee>>> groupedByDeptAndDesignation = employees.stream()
            .collect(Collectors.groupingBy(
                emp -> emp.department, // First level grouping: by department
                Collectors.groupingBy(
                    emp -> emp.designation // Second level grouping: by designation
                )
            ));
        // Print the result
        System.out.println("Grouped by Department and Designation: " + groupedByDeptAndDesignation);
    }
}
Output:
Grouped by Department and Designation: {
    HR={Manager=[Alice], Executive=[Charlie]},
    IT={Developer=[Bob, David]},
    Finance={Analyst=[Eve]}
}
Explanation:
The first groupingBy groups employees by their department.
The second groupingBy further groups employees by their designation within each department.
The result is a nested Map<String, Map<String, List<Employee>>>.
Example 2: Group Words by Length and then by Their Initial Character
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class NestedGroupingWords {
    public static void main(String[] args) {
        // Sample data
        List<String> words = Arrays.asList("apple", "ant", "banana", "cherry", "cat", "dog", "dragonfruit");
        // Nested grouping by word length and then by the first character
        Map<Integer, Map<Character, List<String>>> groupedByLengthAndFirstChar = words.stream()
            .collect(Collectors.groupingBy(
                String::length, // First level grouping: by length of the word
                Collectors.groupingBy(
                    word -> word.charAt(0) // Second level grouping: by the first character
                )
            ));
        // Print the result
        System.out.println("Grouped by Length and First Character: " + groupedByLengthAndFirstChar);
    }
}
Output:
Grouped by Length and First Character: {
    3={a=[ant], c=[cat], d=[dog]},
    5={a=[apple]},
    6={b=[banana]},
    7={c=[cherry]},
    11={d=[dragonfruit]}
}
Explanation:
The first groupingBy groups words by their length.
The second groupingBy groups words within each length group by their first character.