Java has been striving hard to reduce the verbosity of its code in the latest features. Once such feature introduced is the teeing() method introduced in Java 12.
A tee means this :

Input from two different sources can be combined and fed into another source.
Let’s say you have a list of employees.
You want to retrieve the list of all their names.
You also want to take the total count of male employees.
Can you do this in a single operation in Java?
Yes!
You can do it through Collectors.teeing() introduced in Java 12.
Traditionally you can get both the results through a for loop like this:
Employee employee1 = new Employee("Reshma", "Female"); Employee employee2 = new Employee("Rathish", "Male"); Employee employee3 = new Employee("Kumutha", "Female"); Employee employee4 = new Employee("Rathish", "Male"); Employee employee5 = new Employee("Rodrina", "Female"); Employee employee6 = new Employee("Kalyani", "Female"); List<Employee> employees = Arrays.asList(employee1, employee2, employee3, employee4, employee5, employee6); // USING TRADITIONAL FOR LOOP int maleCount = 0; List<String> names = new ArrayList<String>(); for (Employee employee : employees) { if (employee.getGender().equals("Male")) { maleCount++; } names.add(employee.getName()); } System.out.println("Male Count: " + maleCount + " Employee names: " + names);
It took two variable declarations , a for loop , an if loop , increment operation , manual addition of names to a list and finally a print operation.
Using streams and without Collectors.teeing() method it could be achieved like this:
Long maleEmployeesCount = employees.stream().filter(e -> e.getGender().equals("Male")) .collect(Collectors.counting()); List<String> employeeNames = employees.stream().map(e -> e.getName()).collect(Collectors.toList()); System.out.println("Male Count: " + maleEmployeesCount + " Employee names: " + employeeNames);
In the above code first, the employee list is converted to a stream using stream() method and then applied the filter() operation to filter male employees and the collect() method is called using Collectors.counting() method as a parameter to find their count.
And then again a new stream is created and the names are mapped out and collected in a list.
Using streams took less lines of code and no loops.
We can avoid the second streaming using Collectors.teeing() like this:
var result = employees.stream().collect( Collectors.teeing( Collectors.filtering(e -> e.getGender().equals("Male"), Collectors.counting()), Collectors.mapping(e -> e.getName(), Collectors.toList()), (mCount, nameList) -> { return "Male Count: " + mCount + " Employee names: " + nameList; } )); System.out.println(result);
Though a longer line , it took a single line of code to compute both the results.
Lets look at the code in details.
The employee list is first converted into a stream and then collect method is called on it:
var result = employees.stream.collect(--pass a collector --here);
the collect method expects a collector and we pass the teeing collector here:
var result = employees.stream.collect(Collectors.teeing(--pass arguments here--));
The Collectors.teeing method takes in three arguments:
- The first collector which performs the first operation (finding out the total count of male employees in our case)
- The second collector which performs the second operation (finding out the list of employee names in our case)
- A BiFunctional interface which takes two arguments (the outputs of the above two collectors) and returns a result (computed through lamda expression)
Here is the first collector passed :
Collectors.filtering(e -> e.getGender().equals("Male"), Collectors.counting())
This does the same thing as shown in the previous example using the first stream: finds the count of male employees and returns them as a collector.
Below is the second collector passed:
Collectors.mapping(e -> e.getName(), Collectors.toList())
This does the same things as shown in the previous example using the second stream : collect all names in a list and return them as a collector.
Below is the lamda expression passed as a third argument :
(mCount, nameList) -> { return "Male Count: " + mCount + " Employee names: " + nameList; }
This just collects the output from the first two collectors and concatenates them in a string. We could do more things with them and fully utilize the power of teeing().
All the above three types of code(traditional for loop, streams without teeing() , streams with teeing()) produce the same output:
Male Count: 2 Employee names: [Reshma, Rathish, Kumutha, Rathish, Rodrina, Kalyani]
Three different ways to solve the same problem!
Here is the full code:
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collector; import java.util.stream.Collectors; public class Client { public static void main(String a[]) { Employee employee1 = new Employee("Reshma", "Female"); Employee employee2 = new Employee("Rathish", "Male"); Employee employee3 = new Employee("Kumutha", "Female"); Employee employee4 = new Employee("Rathish", "Male"); Employee employee5 = new Employee("Rodrina", "Female"); Employee employee6 = new Employee("Kalyani", "Female"); List<Employee> employees = Arrays.asList(employee1, employee2, employee3, employee4, employee5, employee6); // USING TRADITIONAL FOR LOOP int maleCount = 0; List<String> names = new ArrayList<String>(); for (Employee employee : employees) { if (employee.getGender().equals("Male")) { maleCount++; } names.add(employee.getName()); } System.out.println("Male Count: " + maleCount + " Employee names: " + names); // USING STREAMS AND COLLECTOR API (WITHOUT TEEING) Long maleEmployeesCount = employees.stream().filter(e -> e.getGender().equals("Male")) .collect(Collectors.counting()); List<String> employeeNames = employees.stream().map(e -> e.getName()).collect(Collectors.toList()); System.out.println("Male Count: " + maleEmployeesCount + " Employee names: " + employeeNames); // USING STREAMS AND COLLECTOR TEEING. var result = employees.stream().collect( Collectors.teeing( Collectors.filtering(e -> e.getGender().equals("Male"), Collectors.counting()), Collectors.mapping(e -> e.getName(), Collectors.toList()), (mCount, nameList) -> { return "Male Count: " + mCount + " Employee names: " + nameList; } )); System.out.println(result); } }
The Employee class:
public class Employee { private String name; private String gender; public Employee(String name, String gender) { this.name = name; this.gender = gender; } @Override public String toString() { return "Employee [name=" + name + ", gender=" + gender + "]"; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getGender() { return gender; } public void setGender(String gender) { this.gender = gender; } }
Teeing() makes java code less verbose and saves time typing.