Back to Spliterator
Q1Question 1 of 2

What is a Spliterator?

Key Concepts
  • Spliterator (Splittable Iterator) is a Java 8 iterator designed for parallel processing, used internally by the Stream API and parallel streams.
  • tryAdvance(Consumer) processes one element and returns true if more remain, similar to a combined hasNext() and next().
  • trySplit() divides the remaining elements into two halves: the current Spliterator keeps one half, and a new Spliterator is returned for the other.
  • Parallel streams use trySplit() recursively via the ForkJoinPool to distribute work across threads.
  • estimateSize() returns the estimated number of remaining elements, enabling pre-allocation of result arrays.
  • characteristics() returns flags (ORDERED, SIZED, SORTED, DISTINCT) that the stream pipeline uses for optimisation.
  • Every Collection provides a default spliterator() method; ArrayList splits efficiently by index range, while LinkedList does not split well.

Definition

Spliterator is a specialised iterator that can split its elements into two halves for parallel processing, used internally by Java's Stream API and parallel streams.
  • tryAdvance(Consumer) – processes one element and returns true if more elements remain; combines hasNext() and next() into a single method
  • trySplit() – splits the remaining elements: returns a new Spliterator for one portion while the current Spliterator retains the rest; returns null if splitting is not possible
  • estimateSize() – returns the estimated count of remaining elements (exact if SIZED characteristic is present)
  • characteristics() – returns a bitmask of properties (ORDERED, SIZED, SORTED, DISTINCT, NONNULL, IMMUTABLE, CONCURRENT, SUBSIZED)
  • forEachRemaining(Consumer) – processes all remaining elements; default implementation calls tryAdvance repeatedly
  • Internal use – parallel streams call trySplit() recursively to distribute chunks to ForkJoinPool worker threads

At National Bank of India, an Iterator is one clerk processing a stack of 1000 transaction receipts one by one. A Spliterator is a splitting clerk: when the manager asks, the clerk divides the stack in half and hands 500 receipts to a colleague. Each clerk processes their half simultaneously. When both finish, the results are combined. This is exactly how parallel streams work internally: the Spliterator decides how to split the data for parallel processing.

Understanding the Concepts

What is a Spliterator?

Spliterator (Splittable Iterator) was introduced in Java 8 alongside the Stream API. Unlike a regular Iterator that processes elements sequentially, a Spliterator can split its elements into two halves, enabling parallel processing. Every Collection provides a default spliterator() method.

JAVA
package com.bank.demo;

import java.util.List;
import java.util.Spliterator;

public class SpliteratorBasicDemo {
    public static void main(String[] args) {
        List<String> accounts = List.of("SAV2001", "CUR3001", "FD4001", "SAV2002", "CUR3002");
        Spliterator<String> spliterator = accounts.spliterator();

        System.out.println("Estimated size: " + spliterator.estimateSize());
        System.out.println("Characteristics: " + spliterator.characteristics());

        // tryAdvance: process one element at a time
        System.out.println("\nProcessing one by one:");
        spliterator.tryAdvance(acc -> System.out.println("  Processed: " + acc));
        spliterator.tryAdvance(acc -> System.out.println("  Processed: " + acc));

        System.out.println("Remaining estimate: " + spliterator.estimateSize());
    }
}
TEXT
Estimated size: 5
Characteristics: 16721

Processing one by one:
  Processed: SAV2001
  Processed: CUR3001
Remaining estimate: 3

How does trySplit work?

trySplit divides the remaining elements approximately in half. The current Spliterator retains one portion, and a new Spliterator is returned for the other portion. This enables the ForkJoinPool to process both halves in parallel.

JAVA
package com.bank.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.Spliterator;

public class TrySplitDemo {
    public static void main(String[] args) {
        List<String> accounts = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            accounts.add("ACC-" + String.format("%03d", i));
        }

        Spliterator<String> original = accounts.spliterator();
        System.out.println("Original size: " + original.estimateSize());

        Spliterator<String> firstHalf = original.trySplit();
        System.out.println("After split:");
        System.out.println("  First half size: " + firstHalf.estimateSize());
        System.out.println("  Second half size: " + original.estimateSize());

        System.out.println("\nFirst half elements:");
        firstHalf.forEachRemaining(acc -> System.out.println("  " + acc));

        System.out.println("Second half elements:");
        original.forEachRemaining(acc -> System.out.println("  " + acc));
    }
}
TEXT
Original size: 10
After split:
  First half size: 5
  Second half size: 5

First half elements:
  ACC-001
  ACC-002
  ACC-003
  ACC-004
  ACC-005
Second half elements:
  ACC-006
  ACC-007
  ACC-008
  ACC-009
  ACC-010

How do parallel streams use Spliterator internally?

When you call parallelStream(), the Stream API obtains a Spliterator from the collection and uses the ForkJoinPool to recursively split and process the data.

JAVA
package com.bank.demo;

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

public class ParallelStreamSpliteratorDemo {

    record Account(String id, BigDecimal balance) {}

    public static void main(String[] args) {
        List<Account> accounts = new ArrayList<>();
        for (int i = 1; i <= 1000; i++) {
            accounts.add(new Account(
                "ACC-" + String.format("%04d", i),
                new BigDecimal(i * 1000)
            ));
        }

        // Sequential: uses Spliterator.forEachRemaining internally
        BigDecimal seqTotal = accounts.stream()
            .map(Account::balance)
            .reduce(BigDecimal.ZERO, BigDecimal::add);

        // Parallel: uses Spliterator.trySplit to divide work
        BigDecimal parTotal = accounts.parallelStream()
            .map(Account::balance)
            .reduce(BigDecimal.ZERO, BigDecimal::add);

        System.out.println("Sequential total: " + seqTotal);
        System.out.println("Parallel total: " + parTotal);
        System.out.println("Equal: " + seqTotal.equals(parTotal));
    }
}
TEXT
Sequential total: 500500000
Parallel total: 500500000
Equal: true

How is Spliterator different from Iterator?

| Feature | Iterator | Spliterator |
|---|---|---|
| Split for parallel | No | Yes (trySplit) |
| Traverse one element | next() + hasNext() | tryAdvance(Consumer) |
| Bulk traverse | No built-in | forEachRemaining(Consumer) |
| Size estimate | No | estimateSize() |
| Characteristics | No | characteristics() flags |
| Introduced | Java 2 | Java 8 |
| Used by | for-each loops, Iterator pattern | Stream API, parallel streams |

JAVA
package com.bank.demo;

import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;

public class IteratorVsSpliteratorDemo {
    public static void main(String[] args) {
        List<String> accounts = List.of("SAV2001", "CUR3001", "FD4001");

        // Iterator: sequential only
        System.out.println("Iterator:");
        Iterator<String> it = accounts.iterator();
        while (it.hasNext()) {
            System.out.println("  " + it.next());
        }

        // Spliterator: can split for parallel
        System.out.println("\nSpliterator:");
        Spliterator<String> sp = accounts.spliterator();
        sp.forEachRemaining(acc -> System.out.println("  " + acc));
    }
}
TEXT
Iterator:
  SAV2001
  CUR3001
  FD4001

Spliterator:
  SAV2001
  CUR3001
  FD4001

What are common mistakes?

Mistake: Expecting trySplit to always split evenly

JAVA
// trySplit may return null if the source is too small or cannot be split
Spliterator<String> sp = List.of("SAV2001").spliterator();
Spliterator<String> split = sp.trySplit();
System.out.println("Split result: " + split); // null: too few elements to split

Mistake: Using Spliterator directly instead of Stream API

JAVA
// WRONG approach: manually managing Spliterators is verbose and error-prone
Spliterator<String> sp = accounts.spliterator();
// ... manual splitting, threading, merging
JAVA
// CORRECT: let the Stream API handle Spliterator internally
accounts.parallelStream()
    .filter(acc -> acc.startsWith("SAV"))
    .forEach(System.out::println);

When should you create a custom Spliterator?

Create a custom Spliterator when you have a non-standard data source (a database cursor, a network stream, a custom tree structure) that you want to expose to the Stream API. Implement Spliterator with appropriate characteristics.

What should a senior engineer know?

  • ArrayList vs LinkedList splitting. ArrayList's Spliterator splits by index range (O(1)), making it highly efficient for parallel streams. LinkedList's Spliterator traverses to the midpoint (O(n/2)), making it poor for parallel splitting.
  • HashMap Spliterator. HashMap splits by bucket ranges, which can be uneven if the data distribution is skewed.
  • trySplit contract. After trySplit, the original Spliterator covers a strict suffix and the returned one covers a strict prefix. estimateSize of both should sum to the original.
  • StreamSupport.stream(). To create a Stream from a custom Spliterator, use StreamSupport.stream(spliterator, parallel).

What are common interview follow-up questions?

  • "What is the difference between Iterator and Spliterator?" : Spliterator can split for parallel processing via trySplit(), has estimateSize(), and reports characteristics().
  • "When is trySplit called?" : By the ForkJoinPool when processing a parallel stream. It recursively splits until chunks are small enough.
  • "Can trySplit return null?" : Yes. If the source is too small or indivisible, trySplit returns null to indicate no splitting is possible.
  • "Do you interact with Spliterator directly?" : Rarely. The Stream API manages Spliterators internally. You create custom Spliterators only for non-standard data sources.

Visual Overview

DIAGRAM
  Spliterator Parallel Processing Flow
  =====================================

  accounts.parallelStream().filter(...).collect(...)

  Step 1: Get Spliterator
  ┌─────────────────────────────────────────┐
  │ Spliterator [ACC-001 ... ACC-1000]      │
  │ estimateSize() = 1000                   │
  └─────────────────────────────────────────┘

  Step 2: ForkJoinPool calls trySplit() recursively
  ┌─────────────────────┐  ┌──────────────────────┐
  │ [ACC-001...ACC-500]  │  │ [ACC-501...ACC-1000] │
  └──────────┬──────────┘  └──────────┬───────────┘
             │                        │
        trySplit()               trySplit()
       ┌─────┴─────┐           ┌──────┴──────┐
   [1..250]    [251..500]  [501..750]   [751..1000]

  Step 3: Each chunk processed by a ForkJoinPool thread
  Thread-1: filter [1..250]
  Thread-2: filter [251..500]
  Thread-3: filter [501..750]
  Thread-4: filter [751..1000]

  Step 4: Results merged
  ┌────────────────────────────┐
  │ Combined filtered results   │
  └────────────────────────────┘

Key Takeaway

Spliterator is the parallel-aware iterator that powers Java's Stream API. tryAdvance processes one element, trySplit divides the remaining elements for parallel processing, estimateSize reports the count, and characteristics tells the stream pipeline how to optimise. When you call parallelStream(), the ForkJoinPool recursively splits the Spliterator into smaller chunks, processes each chunk on a separate thread, and merges the results.

You rarely interact with Spliterators directly. The Stream API handles all the splitting and merging internally. The main reason to understand Spliterators is to know why certain collections parallelise better than others: ArrayList splits by index range (fast), while LinkedList traverses to the midpoint (slow). This knowledge helps you choose the right collection for parallel workloads.

At National Bank of India, when Pankaj needed to compute the total balance across 100,000 accounts in the end-of-day batch, he used parallelStream(). Internally, the ArrayList's Spliterator split the account list across ForkJoinPool threads, reducing the computation time proportionally to the number of cores.

Remember one sentence: Spliterator is a splittable iterator used internally by parallel streams, where trySplit divides data for concurrent processing and characteristics guide stream optimisations.

---

Share
Use ← → arrow keys to navigate