Study guide

9.2 The Collectors API: Downstream Reductions, Grouping, and Partitioning

Oracle Java SE 17 Developer Certified Professional 1Z0-829

Track your progress as you read

Finished sections get a check mark.

1 Introduction: The 1Z0-829 Exam

About the Java SE 17 Developer (1Z0-829) Exam

2 Chapter 1: Java Fundamentals & Language Basics

1.1 Primitive Types, Literals, Wrapper Classes, and Type Promotion/Casting 1.2 Local Variable Type Inference (`var`) Rules and Scoping 1.3 Operators, Precedence, Evaluation Order, and Short-Circuit Evaluation 1.4 Text Blocks, String Immutability, and StringBuilder APIs

3 Chapter 2: Program Flow & Decision Constructs

2.1 Conditional Branching with if/else and the Ternary Operator 2.2 Switch Statements and Switch Expressions 2.3 Iteration Constructs, Loop Control, and Branching Labels

4 Chapter 3: Object-Oriented Design & Class Mechanics

3.1 Class Declarations, Field/Method Signatures, and Access Modifiers 3.2 Constructors, Overloading, Initialization Order, and Garbage Collection 3.3 Inheritance, Method Overriding, Polymorphism, and `super`/`this` 3.4 Abstract Classes, Static Members, and Object Methods (`equals`, `hashCode`, `toString`)

5 Chapter 4: Advanced OOP: Interfaces, Records & Sealed Types

4.1 Interface Architecture: Default, Static, and Private Methods 4.2 Enumerations: Constants, Constructors, Methods, and Custom Bodies 4.3 Records: Canonical/Compact Constructors, Components, and Immutability 4.4 Sealed Classes and Interfaces: `permits`, Hierarchy Constraints, and Modifiers

6 Chapter 5: Pattern Matching & Nested Types

5.1 Pattern Matching for `instanceof` and Flow Scoping Mechanics 5.2 Nested Types: Member Inner, Static Nested, Local, and Anonymous Classes 5.3 Defensive Programming, Immutability Patterns, and Validation

7 Chapter 6: Generics, Collections & Arrays

6.1 Generic Classes, Interfaces, Methods, and Type Erasure 6.2 Bounded Type Parameters and Wildcards (PECS Rule) 6.3 Collections Framework: List, Set, Queue, and Deque APIs 6.4 The Map Interface, Sorting, Comparable, and Comparator 6.5 Arrays: Declaration, Initialization, Multidimensional Arrays, and the Arrays Utility Class

8 Chapter 7: Functional Programming & Lambda Expressions

7.1 Functional Interfaces and Lambda Syntax Rules 7.2 Standard Functional Interfaces in `java.util.function` 7.3 Method References: Static, Bound, Unbound, and Constructor References

9 Chapter 8: The Stream API

8.1 Stream Lifecycle: Creation, Pipeline Architecture, and Lazy Evaluation 8.2 Intermediate Stream Operations: Filtering, Mapping, Sorting, and Slicing 8.3 Terminal Stream Operations: Reduction, Matching, Finding, and Collecting 8.4 Primitive Streams (`IntStream`, `LongStream`, `DoubleStream`) and Numeric Statistics

10 Chapter 9: Advanced Streams & Collectors

9.1 The Optional Class: Safe Null Handling, Transformation, and Chaining 9.2 The Collectors API: Downstream Reductions, Grouping, and Partitioning 9.3 Parallel Streams: Fork/Join Pool, Spliterators, and Concurrency Pitfalls

11 Chapter 10: Exception Handling

10.1 The Java Exception Hierarchy: Checked vs. Unchecked Exceptions and Errors 10.2 Exception Constructs: `try-catch-finally`, Multi-Catch, and Rethrowing 10.3 Try-With-Resources, `AutoCloseable`, Suppressed Exceptions, and Custom Exceptions

12 Chapter 11: Date/Time API & Localization

11.1 Date and Time Core Types: `LocalDate`, `LocalTime`, `LocalDateTime` 11.2 Timestamps, Intervals, and Durations: `Instant`, `Period`, and `Duration` 11.3 Time Zones, Daylight Saving Time (DST) Offsets, and `ZonedDateTime` 11.4 Internationalization and Localization: `Locale`, `ResourceBundle`, and Formatters 11.5 Formatting and Parsing Messages, Numbers, Percentages, and Localized Dates

13 Chapter 12: Java Platform Module System (JPMS)

12.1 Module Architecture, `module-info.java`, and Module Paths 12.2 Module Directives: `requires`, `requires transitive`, `exports`, and Qualified Exports 12.3 Reflection, Deep Encapsulation, `opens`, and Open Modules 12.4 Services in JPMS (`uses`, `provides...with`), Migration Paths, and Tooling (`jlink`, `jdeps`) 12.5 Compiling and Packaging Code: Non-Modular JARs, Modular JARs, and Migration Strategies

14 Chapter 13: Concurrency & Multithreading

13.1 Thread Fundamentals, `Runnable`, `Callable`, and Thread Lifecycle 13.2 Memory Visibility, `volatile`, Intrinsic Locks, and Synchronization 13.3 The Concurrency Framework: `ExecutorService`, Thread Pools, and `Future` 13.4 Concurrent Collections, Atomic Variables, Explicit Locks, and `CompletableFuture`

15 Chapter 14: Java I/O and NIO.2

14.1 Classical I/O: Byte and Character Streams, Readers, Writers, and Buffering 14.2 Object Serialization, `Serializable`, `transient`, and `serialVersionUID` 14.3 NIO.2 Architecture: `Path`, `Paths`, and Basic `Files` Operations 14.4 Advanced NIO.2: Directory Walking, Streams (`Files.lines`, `walk`), and `WatchService`

16 Chapter 15: Accessing Databases with JDBC

15.1 JDBC Architecture, Drivers, `DriverManager`, and `DataSource` Connections 15.2 SQL Execution: `Statement`, `PreparedStatement`, and `CallableStatement` 15.3 `ResultSet` Navigation, Data Types, and Transaction Management (Commit, Rollback, Savepoints)

17 Chapter 16: Annotations & the Java Logging API

16.1 Standard Annotations: @Override, @FunctionalInterface, @Deprecated, @SuppressWarnings, and @SafeVarargs 16.2 Java Logging API Basics: Logger, Levels, Handlers, and Configuration

9.2 The Collectors API: Downstream Reductions, Grouping, and Partitioning

Key Takeaways Explain This Topic AI

The Collector<T, A, R> interface orchestrates mutable container reductions via four components: supplier(), accumulator(), combiner(), and finisher(), parameterized with characteristics.

The 2-argument Collectors.toMap() throws an IllegalStateException upon encountering duplicate keys, and throws NullPointerException if any valueMapper evaluates to null.

Numeric collectors (summingInt, averagingDouble, summarizingLong) and string joining(delimiter, prefix, suffix) perform specialized aggregations directly within terminal operations.

Multi-level groupingBy supports 1-, 2-, and 3-argument overloads allowing custom Map container suppliers (e.g., TreeMap::new) and arbitrary downstream reduction collectors.

Collectors.partitioningBy(Predicate) always returns a Map<Boolean, D> containing both true and false keys regardless of element matches, while Collectors.teeing() executes two collectors on a single stream pass and merges their results.

Last updated: October 2026

9.2 The Collectors API: Downstream Reductions, Grouping, and Partitioning

In Java Stream pipelines, terminal operations convert intermediate streams into concrete domain collections, primitive aggregations, or complex grouped data structures. While Stream.reduce() performs immutable folding, the mutable reduction mechanism provided by Stream.collect(Collector) is the industry standard for high-performance data processing. The utility class java.util.stream.Collectors provides a rich set of pre-built, reusable implementations. The 1Z0-829 examination tests your ability to choose, configure, and nest collectors, handle map key collisions, apply downstream reductions, and use modern additions like partitioningBy and Java 12's teeing collector.

The Architecture of Collector<T, A, R>

A Collector encapsulates the recipe for performing a mutable reduction into an intermediate accumulator container A , ultimately producing a final result R from elements of type T .

The Collector<T, A, R> interface defines four core functional components:

Supplier<A> supplier() : Creates and returns a new, mutable intermediate container (e.g., ArrayList::new , StringBuilder::new , or HashMap::new ).

BiConsumer<A, T> accumulator() : Folds a single stream element T into the mutable container A (e.g., List::add ).

BinaryOperator<A> combiner() : Merges two partial accumulation containers into one. This method is critical for parallel streams when different threads process distinct sub-tasks.

Function<A, R> finisher() : Transforms the intermediate container A into the final result type R . If container A requires no final transformation, the collector reports the IDENTITY_FINISH characteristic and the identity function is used.

Collectors can also report optimization characteristics via characteristics() :

IDENTITY_FINISH : Indicates the finisher is an unchecked identity cast from A to R .

CONCURRENT : Indicates the accumulator can be invoked concurrently by multiple threads on a shared container without synchronization (used primarily with concurrent data structures).

UNORDERED : Indicates the collection operation does not depend on preserving encounter order.

Basic Collection Targets: Lists, Sets, and Custom Collections List<String> items = Stream.of("alpha", "beta", "gamma").toList(); // Java 16+ unmodifiable list

  • // 1. Collectors.toList(): Typically returns a modifiable ArrayList (no guarantee)
  • List<String> modifiableList = items.stream().collect(Collectors.toList());
  • // 2. Collectors.toSet(): Collects into a HashSet (eliminates duplicates, drops order)
  • Set<String> set = items.stream().collect(Collectors.toSet());
  • // 3. Collectors.toCollection(Supplier): Collects into an explicit collection type
  • TreeSet<String> sortedSet = items.stream()
  • .collect(Collectors.toCollection(TreeSet::new));
  • LinkedList<String> linkedList = items.stream()
  • .collect(Collectors.toCollection(LinkedList::new));
  • // 4. Collectors.toUnmodifiableList() / toUnmodifiableSet() (Java 10+)
  • List<String> immutableList = items.stream().collect(Collectors.toUnmodifiableList());
  • Map Collection and the Duplicate Key Trap

Transforming stream elements into a Map using Collectors.toMap() requires careful attention to key collisions and null handling.

The Three toMap() Overloads

2-Argument : toMap(keyMapper, valueMapper)

Uses elements to generate keys and values.

Throws IllegalStateException if duplicate keys are generated!

3-Argument : toMap(keyMapper, valueMapper, mergeFunction)

When duplicate keys collide, invokes mergeFunction.apply(existingVal, newVal) to resolve the collision.

4-Argument : toMap(keyMapper, valueMapper, mergeFunction, mapSupplier)

Supplies a custom Map implementation (e.g., TreeMap::new , LinkedHashMap::new ). record Employee(int id, String department, double salary) {}

  • List<Employee> staff = List.of(
  • new Employee(101, "Engineering", 90000),
  • new Employee(102, "HR", 65000),
  • new Employee(103, "Engineering", 95000)
  • );
  • // 2-arg toMap: id is unique -> SUCCESS
  • Map<Integer, Double> idSalaryMap = staff.stream()
  • .collect(Collectors.toMap(Employee::id, Employee::salary));

// 2-arg toMap on duplicate department -> THROWS IllegalStateException!

  • // Map<String, Double> deptSalaryMap = staff.stream()
  • // .collect(Collectors.toMap(Employee::department, Employee::salary));
  • // 3-arg toMap with mergeFunction: keep the higher salary on collision
  • Map<String, Double> topDeptSalary = staff.stream()
  • .collect(Collectors.toMap(
  • Employee::department,
  • Employee::salary,
  • (existing, incoming) -> Math.max(existing, incoming)
  • ));
  • // 4-arg toMap: preserves sorted key order via TreeMap
  • TreeMap<String, Double> sortedDeptMap = staff.stream()
  • .collect(Collectors.toMap(
  • Employee::department,
  • Employee::salary,
  • Double::sum,
  • TreeMap::new
  • ));

Warning

The toMap() Null Value Trap Collectors.toMap() rejects null values: if any valueMapper returns null , the collection throws a NullPointerException even though the underlying HashMap itself would permit null values! If your stream values can be null, collect into an existing map manually or filter out nulls.

  • String Joining and Numeric Aggregations
  • Collectors.joining()

Joins elements of CharSequence (or String ) into a single output string.

joining() : Concatenates elements with no delimiter.

joining(delimiter) : Concatenates elements separated by the delimiter.

joining(delimiter, prefix, suffix) : Concatenates with delimiter, enclosed by prefix and suffix. String csv = Stream.of("red", "green", "blue")

  • .collect(Collectors.joining(", ", "[", "]")); // "[red, green, blue]"
  • Numeric and Statistical Collectors

Rather than mapping to primitive streams ( mapToInt() ), you can aggregate directly in collect() :

counting() : Returns Collector<T, ?, Long> counting elements.

summingInt(ToIntFunction) , summingLong() , summingDouble() : Returns the sum as boxed Integer , Long , or Double .

averagingInt() , averagingLong() , averagingDouble() : Always returns Double (even for averagingInt() ).

summarizingInt() , summarizingLong() , summarizingDouble() : Returns rich summary objects ( IntSummaryStatistics , DoubleSummaryStatistics ) providing count , sum , min , max , and average in a single pass. IntSummaryStatistics stats = staff.stream()

  • .collect(Collectors.summarizingInt(e -> (int) e.salary()));
  • System.out.println("Max: " + stats.getMax() + ", Avg: " + stats.getAverage());
  • Post-Processing: Collectors.collectingAndThen()

Performs a collection and then immediately applies a finishing transformation function to the accumulated result: List<String> readOnlyList = staff.stream()

  • .map(Employee::department)
  • .collect(Collectors.collectingAndThen(
  • Collectors.toList(),
  • Collections::unmodifiableList
  • ));
  • Classification with groupingBy

Collectors.groupingBy() organizes stream elements into groups based on a classification function, analogous to GROUP BY in SQL.

  • The Three groupingBy Overloads
  • 1-Argument : groupingBy(classifier)

Defaults to a HashMap<K, List<T>> .

2-Argument : groupingBy(classifier, downstreamCollector)

Classifies by key, but processes values in each bucket using the specified downstream collector instead of accumulating into a List .

  • 3-Argument : groupingBy(classifier, mapSupplier, downstreamCollector)
  • Allows choosing the concrete Map class (e.g., TreeMap::new ). // 1-arg: Map<String, List<Employee>>
  • Map<String, List<Employee>> byDept = staff.stream()
  • .collect(Collectors.groupingBy(Employee::department));
  • // 2-arg: Map<String, Long> (count employees per department)
  • Map<String, Long> headCount = staff.stream()
  • .collect(Collectors.groupingBy(Employee::department, Collectors.counting()));
  • // 2-arg: Map<String, Double> (average salary per department)
  • Map<String, Double> avgSalary = staff.stream()
  • .collect(Collectors.groupingBy(Employee::department, Collectors.averagingDouble(Employee::salary)));
  • // 3-arg: TreeMap<String, Set<Double>>
  • TreeMap<String, Set<Double>> salariesByDept = staff.stream()
  • .collect(Collectors.groupingBy(
  • Employee::department,
  • TreeMap::new,
  • Collectors.mapping(Employee::salary, Collectors.toSet())
  • ));
  • Downstream Collectors: mapping() , filtering() , and flatMapping()

mapping(mapper, downstream) : Transforms bucket elements before downstream collection.

filtering(predicate, downstream) (Java 9+): Filters bucket elements. Crucial Advantage : Unlike a pipeline .filter() which eliminates keys entirely if all their items are dropped, filtering() preserves the key with an empty downstream collection!

flatMapping(mapper, downstream) (Java 9+): Flattens nested collections generated from bucket elements.

Partitioning with partitioningBy

Collectors.partitioningBy() is a specialized form of grouping that divides stream elements into two categories based on a Predicate<? super T> .

1-Argument : partitioningBy(predicate) -> Returns Map<Boolean, List<T>> .

2-Argument : partitioningBy(predicate, downstreamCollector) -> Returns Map<Boolean, D> . Map<Boolean, List<Employee>> highEarners = staff.stream()

.collect(Collectors.partitioningBy(e -> e.salary() >= 90000));

  • // highEarners.get(true) contains employees earning >= 90000
  • // highEarners.get(false) contains employees earning < 90000

Warning

The Partitioning Key Invariant Rule A frequent trap concerns the keys present in a partitioned map. While groupingBy only creates keys that exist in the stream elements, partitioningBy ALWAYS contains BOTH Boolean.TRUE and Boolean.FALSE keys , even if all elements match the predicate (or if no elements match). The absent partition key will simply map to an empty list or the empty value of the downstream collector.