Back to Stream: Performance & Pitfalls
Q2Question 2 of 5

When should you use toArray() vs collect(toList())?

Key Concepts
  • Default to collect(toList()) for most code because List is flexible, generic-friendly, and composable
  • Use toArray(T[]::new) only when an API requires an array, for varargs, or for legacy interoperability
  • Primitive streams (IntStream, LongStream) have toArray() returning int[] or long[] without boxing
  • Java 16's Stream.toList() returns an unmodifiable list, unlike Collectors.toList() which gives mutable ArrayList
  • Converting a list to an array later is trivial with list.toArray(T[]::new) if needed
  • Arrays have fixed size and poor generics support, making them less convenient for general use

Definition

Default to `collect(toList())` for flexibility; use `toArray()` only for array-demanding APIs, primitives, or varargs.
  • collect(toList()): returns a flexible List that supports generics and integrates with Collections.
  • toArray(T[]::new): returns a fixed-size T[] needed by specific APIs.
  • Primitive streams: IntStream.toArray() returns int[] without boxing.
  • Java 16 toList(): returns an unmodifiable list (different from Collectors.toList()).
  • Rule: start with List, convert to array only when required.

At National Bank of India, Priya keeps results in a flexible folder (List) by default, and only puts them in a fixed box (array) when some legacy system specifically needs that box shape.

Understanding the Concepts

What are the key differences?

| Aspect | collect(toList()) | toArray(T[]::new) |
|---|---|---|
| Result type | List (mutable ArrayList) | T[] (fixed-size array) |
| Generics | Full support | Limited (arrays and generics mix poorly) |
| Modification | Resizable, add/remove | Fixed size, only replace elements |
| Composability | Re-stream, Collections API | Manual iteration |
| Use when | Most application code | API requires array, varargs, primitives |

When should you use toArray?

JAVA
package com.bank.hr;

import java.math.BigDecimal;
import java.util.List;

public class ToArrayVsToList {

    record Employee(String id, String name, BigDecimal salary) {}

    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("SAV2001", "Rajesh",  new BigDecimal("1800000")),
            new Employee("SAV2002", "Priya",   new BigDecimal("1200000")),
            new Employee("SAV2003", "Meera",   new BigDecimal("1500000"))
        );

        // Use case 1: API requires an array
        Employee[] array = staff.stream().toArray(Employee[]::new);
        legacyReport(array);

        // Use case 2: Primitive array without boxing
        int[] salaryInts = staff.stream()
            .mapToInt(e -> e.salary().intValue())
            .toArray();  // int[], no boxing

        // Default: use toList for everything else
        List<Employee> list = staff.stream()
            .filter(e -> e.salary().compareTo(new BigDecimal("1300000")) > 0)
            .toList();
    }

    static void legacyReport(Employee[] employees) {
        System.out.println("Legacy report: " + employees.length + " employees");
    }
}

What about Java 16's Stream.toList()?

JAVA
// Collectors.toList(): returns mutable ArrayList
List<String> mutable = staff.stream().map(Employee::name)
    .collect(java.util.stream.Collectors.toList());
mutable.add("NewName");  // works

// Stream.toList() (Java 16+): returns unmodifiable list
List<String> unmodifiable = staff.stream().map(Employee::name).toList();
// unmodifiable.add("NewName");  // throws UnsupportedOperationException

What are common mistakes?

Mistake: Using toArray when a List would be simpler.

JAVA
// WRONG: unnecessary array conversion
Employee[] arr = staff.stream().filter(predicate).toArray(Employee[]::new);
for (Employee e : arr) { process(e); }

// CORRECT: just use a List
List<Employee> list = staff.stream().filter(predicate).toList();
list.forEach(this::process);

What should a senior engineer know?

Default to List for flexibility. Reserve toArray for: (1) primitive performance via primitive-stream toArray, (2) API/varargs interoperability, and (3) fixed-size contracts. Converting a list to an array later is trivial if needed. Know the difference between Collectors.toList() (mutable) and Stream.toList() (unmodifiable).

What are common interview follow-up questions?

  • "What is the difference between Collectors.toList() and Stream.toList()?" Mutability: Collectors gives ArrayList, Stream.toList() gives unmodifiable.
  • "When does toArray outperform toList?" Rarely: the difference is negligible for reference types. Primitive streams benefit from toArray returning int[]/long[] without boxing.
  • "Can you convert a list to an array?" Yes: list.toArray(Employee[]::new).

Visual Overview

DIAGRAM
  Decision tree:
  ┌─────────────────────────────────────────┐
  │  Do you need the elements?              │
  │  ├── No  → use count()                  │
  │  └── Yes                                │
  │       ├── API requires array?            │
  │       │   └── Yes → toArray(T[]::new)    │
  │       ├── Primitive without boxing?       │
  │       │   └── Yes → IntStream.toArray()  │
  │       └── General use?                   │
  │           └── Yes → toList()             │
  └─────────────────────────────────────────┘

Key Takeaway

Default to collect(toList()) for most application code because a List is flexible, generic-friendly, and composable. Use toArray(T[]::new) only when an API specifically requires an array, for varargs, or for primitive streams where IntStream.toArray() returns an efficient int[] without boxing.

At the National Bank of India, most reporting returns lists, and Rajesh only produces an array for one legacy signature that requires it. Converting a list to an array later is trivial with list.toArray(T[]::new), so starting with a list is the practical default.

Java 16's Stream.toList() returns an unmodifiable list, while Collectors.toList() typically gives a mutable ArrayList. Choose based on whether the caller needs to modify the result.

Remember one sentence: default to toList() for flexibility and use toArray() only for array-demanding APIs, varargs, or primitive streams.

---

Share
Use ← → arrow keys to navigate