Java Spliterator
Last modified: May 8, 2025
The java.util.Spliterator
is a specialized iterator introduced in
Java 8 to efficiently traverse and partition elements of a data source. It is
designed with parallel processing in mind, making it an essential
component of the Streams API. Unlike conventional iterators, a
Spliterator
can divide a data source into multiple segments,
allowing independent processing of each portion and improving performance in
concurrent environments.
One of the key features of Spliterator
is its ability to describe
the nature of the data source through the characteristics
method.
These characteristics include:
ORDERED
- Elements appear in a defined sequenceDISTINCT
- No duplicate elements exist in the sourceSORTED
- Elements are sorted in a specific orderSIZED
- The number of elements can be determinedNONNULL
- No null values are present in the sourceIMMUTABLE
- The data source remains unchangedCONCURRENT
- The source can be safely modified by multiple threads
Understanding these characteristics allows for better optimization of parallel
execution. The metadata provided by the Spliterator
enables the
Java runtime to make informed decisions about how data should be processed
efficiently.
Spliterator Basics
A Spliterator
serves a dual purpose: it facilitates iteration
through elements and enables parallel decomposition of a data source. This is
achieved through a set of core operations:
- tryAdvance: Performs an action on the next available element and moves forward
- trySplit: Divides the remaining elements into separate parts for concurrent processing
- estimateSize: Provides an approximation of the number of elements left to traverse
- characteristics: Returns metadata describing the properties of the data source
Spliterator
instances are commonly obtained from collections using
the spliterator
method, but they are not limited to lists and sets.
They can also be constructed from arrays, streams, I/O channels, or even custom
generator functions. The Streams API internally leverages
Spliterator
to enable efficient parallel computation, making it a
powerful mechanism for processing large datasets in modern applications.
Basic Spliterator Usage
This example demonstrates how to use a Spliterator
to traverse
elements in a List
. Unlike traditional iteration methods,
Spliterator
provides controlled traversal with characteristics that
define its behavior. Here, we create a Spliterator
from a
predefined list of names and process each element one at a time using the
tryAdvance
method.
package com.zetcode; import java.util.List; import java.util.Spliterator; public class Main { public static void main(String[] args) { List<String> names = List.of("John", "Jane", "Doe", "Sarah"); Spliterator<String> spliterator = names.spliterator(); System.out.println("Characteristics: " + spliterator.characteristics()); System.out.println("Estimated size: " + spliterator.estimateSize()); System.out.println("Elements:"); while (true) { boolean advanced = spliterator.tryAdvance(name -> System.out.println("Processing: " + name)); if (!advanced) { break; // Exit loop when all elements are processed } } } }
The output displays key properties of the Spliterator
. The
characteristics value 16464
indicates that the spliterator
maintains element order (ORDERED
), has a known size
(SIZED
), and supports efficient partitioning
(SUBSIZED
). It also estimates that there are four elements in the
sequence.
After listing these attributes, the program proceeds to process each element
individually. The tryAdvance
method ensures that elements are
accessed sequentially, applying the provided action (printing the name) before
advancing. Once all elements are processed, tryAdvance
returns
false
, signaling the loop to terminate.
This approach is useful for structured data traversal where elements need to be
processed independently. Additionally, Spliterator
can be leveraged
for parallel execution when split appropriately, optimizing performance in
multithreaded environments.
Splitting a Spliterator
A Spliterator
is designed for both sequential iteration and
efficient data partitioning, making it an essential tool for optimizing
performance. One of its most powerful features is the trySplit
method, which attempts to divide the Spliterator
into separate
portions. This enables workloads to be processed concurrently, improving
efficiency in applications handling large datasets.
However, it is important to note that simply calling trySplit
does not automatically enable parallel execution. The split
operation only divides the data source into multiple Spliterator
instances, but these instances will still process data sequentially unless
explicitly parallelized using parallel streams or multi-threading
techniques.
In this example, we demonstrate how a Spliterator
can be split into
two portions using trySplit
. The method attempts to partition the
elements of the data source into two roughly equal parts, returning a new
Spliterator
that manages the second half.
package com.zetcode; import java.util.List; import java.util.Spliterator; public class Main { public static void main(String[] args) { List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8); Spliterator<Integer> firstSplit = numbers.spliterator(); Spliterator<Integer> secondSplit = firstSplit.trySplit(); System.out.println("First split:"); firstSplit.forEachRemaining(System.out::println); System.out.println("Second split:"); secondSplit.forEachRemaining(System.out::println); } }
The output illustrates how the original sequence is divided between the two
Spliterator
instances. The exact split point depends on the
implementation, but generally, the method aims for an even partition of the
dataset. However, despite splitting the elements, this example still runs
sequentially—both splits are processed one after the other.
Enabling Parallel Execution
To truly leverage parallel execution, developers need to integrate parallel streams or explicit multi-threading strategies. The easiest way to parallelize processing is using Java's Stream API:
Listnumbers = List.of(1, 2, 3, 4, 5, 6, 7, 8); System.out.println("Processing in parallel:"); numbers.parallelStream().forEach(System.out::println);
The parallelStream
method automatically distributes elements across
multiple threads using Java's Fork/Join framework, ensuring concurrent execution
without manual threading management. This is a recommended approach for parallel
processing when working with collections.
However, when working with Spliterator, parallel execution does not happen
automatically. The trySplit
method divides a data source into
separate Spliterator
instances, but those parts are still processed
sequentially unless explicitly parallelized.
Using Spliterator for Explicit Multi-Threading
For more control over parallel execution, developers can manually create threads
or use an ExecutorService
to process each split independently. This
approach ensures proper parallel execution of segmented data.
package com.zetcode; import java.util.List; import java.util.Spliterator; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { public static void main(String[] args) { List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8); Spliterator<Integer> firstSplit = numbers.spliterator(); Spliterator<Integer> secondSplit = firstSplit.trySplit(); // Using an ExecutorService to process splits in parallel try (ExecutorService executor = Executors.newFixedThreadPool(2)) { executor.submit(() -> { System.out.println("First split processing by " + Thread.currentThread().getName()); firstSplit.forEachRemaining(num -> System.out.println(Thread.currentThread().getName() + " - " + num)); }); executor.submit(() -> { System.out.println("Second split processing by " + Thread.currentThread().getName()); secondSplit.forEachRemaining(num -> System.out.println(Thread.currentThread().getName() + " - " + num)); }); executor.shutdown(); } } }
This implementation correctly enables parallel execution using
Spliterator
. By employing ExecutorService
with a fixed
thread pool, each split is processed independently in a separate thread,
ensuring true concurrency.
Choosing the Right Approach
Parallel streams are the simplest way to execute operations concurrently, making them ideal for general-purpose collection processing. On the other hand, manual threading with Spliterator provides finer control, allowing developers to explicitly distribute workloads across multiple threads.
Understanding the distinction between splitting a Spliterator
and
actually executing operations in parallel is crucial for optimizing performance
in Java applications. By combining trySplit
with parallel
processing strategies, developers can create scalable solutions that efficiently
handle large data sources.
Characteristics of Spliterator
This example demonstrates how to examine the characteristics of a Spliterator. Characteristics provide metadata about the data source that can be used to optimize processing. We create Spliterators from different sources and examine their characteristics.
package com.zetcode; import java.util.HashSet; import java.util.List; import java.util.Spliterator; import java.util.TreeSet; import java.util.stream.Stream; public class Main { public static void main(String[] args) { printCharacteristics("List", List.of(1, 2, 3).spliterator()); printCharacteristics("Set", new HashSet<>(List.of(1, 2, 3)).spliterator()); printCharacteristics("SortedSet", new TreeSet<>(List.of(1, 2, 3)).spliterator()); printCharacteristics("Stream", Stream.of(1, 2, 3).spliterator()); } private static void printCharacteristics(String source, Spliterator<Integer> spliterator) { System.out.println(source + " characteristics:"); System.out.println("ORDERED: " + spliterator.hasCharacteristics(Spliterator.ORDERED)); System.out.println("DISTINCT: " + spliterator.hasCharacteristics(Spliterator.DISTINCT)); System.out.println("SORTED: " + spliterator.hasCharacteristics(Spliterator.SORTED)); System.out.println("SIZED: " + spliterator.hasCharacteristics(Spliterator.SIZED)); System.out.println("NONNULL: " + spliterator.hasCharacteristics(Spliterator.NONNULL)); System.out.println("IMMUTABLE: " + spliterator.hasCharacteristics(Spliterator.IMMUTABLE)); System.out.println("CONCURRENT: " + spliterator.hasCharacteristics(Spliterator.CONCURRENT)); System.out.println("SUBSIZED: " + spliterator.hasCharacteristics(Spliterator.SUBSIZED)); System.out.println(); } }
The output shows how different collection types have different characteristics. For example, a List is ORDERED and SIZED, a Set is DISTINCT, and a SortedSet is also SORTED. These characteristics help the Stream API optimize its operations.
Creating a Custom Spliterator
This example demonstrates how to create a custom Spliterator
to
efficiently generate a sequence of even numbers up to a specified limit. Unlike
traditional iteration mechanisms that rely on lists or arrays,
Spliterator
enables structured traversal of non-collection data
sources, making it highly versatile for handling custom datasets.
In this implementation, a specialized Spliterator
produces even
numbers starting from 0 and incrementing by 2 until reaching the defined maximum
value. Additionally, Spliterator
supports parallel execution by
dividing its workload into independent sections.
import java.util.Spliterator; import java.util.function.Consumer; public class Main { static class EvenNumberSpliterator implements Spliterator<Integer> { private int current = 0; private final int max; public EvenNumberSpliterator(int max) { this.max = max; } @Override public boolean tryAdvance(Consumer<? super Integer> action) { if (current <= max) { action.accept(current); current += 2; return true; } return false; } @Override public Spliterator<Integer> trySplit() { int mid = (max - current) / 2; if (mid <= 1) return null; int newCurrent = current + mid + (mid % 2); EvenNumberSpliterator newSplit = new EvenNumberSpliterator(newCurrent - 2); current = newCurrent; return newSplit; } @Override public long estimateSize() { return (max - current) / 2 + 1; } @Override public int characteristics() { return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.NONNULL; } } public static void main(String[] args) { Spliterator<Integer> spliterator = new EvenNumberSpliterator(20); System.out.println("Even numbers:"); boolean hasMoreElements; do { hasMoreElements = spliterator.tryAdvance(System.out::println); } while (hasMoreElements); // Parallel processing demonstration Spliterator<Integer> parallelSplit = new EvenNumberSpliterator(20); Spliterator<Integer> split1 = parallelSplit.trySplit(); System.out.println("First half:"); split1.forEachRemaining(System.out::println); System.out.println("Second half:"); parallelSplit.forEachRemaining(System.out::println); } }
This custom Spliterator
efficiently generates and processes even
numbers while supporting controlled iteration. The tryAdvance
method ensures sequential traversal of elements one at a time, and instead of an
empty while
loop, the revised implementation now uses a
do-while
loop, ensuring at least one element is processed before
checking termination conditions.
A key feature of this Spliterator
is its ability to facilitate
parallel computation. The trySplit
method divides the number
range into smaller partitions, allowing independent processing. This
functionality is particularly useful in concurrent applications, where workloads
can be efficiently distributed across multiple threads.
Spliterator
is a powerful alternative to traditional iteration
techniques, offering greater flexibility for structured data traversal and
parallel execution. Whether handling large datasets or optimizing performance,
its ability to split work dynamically makes it a valuable tool in modern Java
applications.
Using Spliterator with Streams
This example shows how Spliterator is used internally by the Stream API. We'll create a stream from a Spliterator and demonstrate both sequential and parallel processing. This shows the real power of Spliterator in enabling parallel stream operations.
package com.zetcode; import java.util.List; import java.util.Spliterator; import java.util.stream.Stream; import java.util.stream.StreamSupport; public class Main { public static void main(String[] args) { List<String> words = List.of("apple", "banana", "cherry", "date", "elderberry"); // Create stream from Spliterator Spliterator<String> spliterator = words.spliterator(); Stream<String> stream = StreamSupport.stream(spliterator, false); System.out.println("Sequential processing:"); stream.forEach(System.out::println); // Parallel processing Spliterator<String> parallelSplit = words.spliterator(); Stream<String> parallelStream = StreamSupport.stream(parallelSplit, true); System.out.println("Parallel processing:"); parallelStream.forEach(System.out::println); } }
The example demonstrates how Spliterators enable both sequential and parallel
stream processing. The second parameter to StreamSupport.stream
determines if the stream should be parallel. The Spliterator handles the
underlying data access in both cases.
Spliterator for Primitive Types
Java provides specialized Spliterators for primitive types:
Spliterator.OfInt
, Spliterator.OfLong
, and
Spliterator.OfDouble
. These avoid boxing overhead when processing
primitive values. This example demonstrates using an OfInt Spliterator.
package com.zetcode; import java.util.Arrays; import java.util.Spliterator; import java.util.stream.IntStream; import java.util.stream.StreamSupport; public class Main { public static void main(String[] args) { int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8}; Spliterator.OfInt intSpliterator = Arrays.spliterator(numbers); System.out.println("Characteristics: " + intSpliterator.characteristics()); System.out.println("Estimated size: " + intSpliterator.estimateSize()); // Process elements System.out.println("Elements:"); intSpliterator.forEachRemaining((int value) -> System.out.println("Value: " + value)); // Create IntStream from Spliterator Spliterator.OfInt streamSpliterator = Arrays.spliterator(numbers); IntStream intStream = StreamSupport.intStream(streamSpliterator, true); System.out.println("Sum: " + intStream.sum()); } }
This example shows the specialized OfInt Spliterator
working with
an int array. The forEachRemaining
method takes an IntConsumer,
avoiding boxing. We also demonstrate creating an IntStream from the
Spliterator
for parallel processing of the primitive values.
Source
Java Spliterator Documentation
In this article, we've covered the essential aspects of the Java
Spliterator
interface with practical examples. The Spliterator is a
powerful tool for traversing and partitioning data sources, especially for
parallel processing with the Stream API. Understanding Spliterator helps in
creating efficient, parallel-ready data processing code.
Author
List all Java tutorials.