Back to Blog
Java

Java LongStream: A Practical Guide to Primitive Streams

java longstream: Learn how to use Java LongStream for efficient primitive long operations, including creation, common operations, performance considerations, and pract...

JavaStream APILongStreamPrimitive StreamsFunctional Programming
A Java LongStream pipeline showing primitive long values flowing through map and filter operations.

Java LongStream is a specialized stream type for primitive long values. It is part of the java.util.stream package and provides a way to process sequences of longs without the overhead of boxing to Long objects. This article covers how to create and use LongStream effectively, including common operations, performance implications, and when it is a better choice than Stream<Long>.

Creating a LongStream

LongStream can be created from several sources. The most common is LongStream.range and rangeClosed, which generate a sequence of longs from a start (inclusive) to an end (exclusive or inclusive respectively).

LongStream range = LongStream.range(1, 10); // 1..9 LongStream closedRange = LongStream.rangeClosed(1, 10); // 1..10

You can also create a stream from an array or individual values using of:

LongStream fromArray = LongStream.of(3L, 5L, 8L, 13L); LongStream single = LongStream.of(42L);

The iterate method generates an infinite stream by applying a unary operator repeatedly. It is often used with limit to bound the number of elements:

LongStream powersOfTwo = LongStream.iterate(1L, n -> n * 2).limit(10);

Finally, concat merges two streams into one. This is useful when you need to process disjoint ranges together.

LongStream combined = LongStream.concat(LongStream.range(1, 5), LongStream.range(10, 15));

Core Operations: Mapping, Filtering, and Reducing

LongStream supports the same functional operations as Stream<T>, but with primitive-specialized versions. For example, map takes a LongUnaryOperator and returns a new LongStream:

LongStream squares = LongStream.range(1, 10).map(n -> n * n);

Filtering uses LongPredicate:

LongStream evens = LongStream.range(1, 20).filter(n -> n % 2 == 0);

Reduction operations like sum, min, max, and average are terminal and return a primitive result. The reduce method gives you full control over accumulation:

long total = LongStream.range(1, 100).reduce(0L, (a, b) -> a + b);

For collecting into a collection, you can use boxed() to convert to Stream<Long> and then apply collect. This is necessary because LongStream does not have a collect(Collector) method.

List<Long> list = LongStream.range(1, 10).boxed().collect(Collectors.toList());

Performance Considerations: Primitive vs Boxed Streams

The primary reason to use LongStream is to avoid the overhead of boxing and unboxing. When you use Stream<Long>, each element is a Long object, which adds memory allocation and garbage collection pressure. LongStream operates on raw long values directly, which can lead to significant performance improvements in tight loops or when processing large datasets.

Consider a simple sum of one million longs. With Stream<Long>, each value must be boxed into a Long object, stored in memory, and later unboxed during the sum. With LongStream, the values remain as primitive longs throughout the pipeline. The difference is most noticeable when the stream is large and the operations are simple.

Parallel execution also benefits from LongStream because the splitting and combining logic works on primitive arrays and avoids object references. The parallel() method works the same way, but the underlying fork-join tasks handle primitive chunks more efficiently.

Handling Edge Cases and Parallel Streams

An empty LongStream is valid and can be created with LongStream.empty(). Terminal operations like sum return 0, count returns 0, and min/max return an OptionalLong that is empty. When using reduce, you must supply an identity value, as with any empty stream.

Overflow is a real concern when working with longs. Operations like sum can overflow silently, producing a negative result. If your data may exceed Long.MAX_VALUE, consider using BigInteger or checking for overflow explicitly. The stream API does not provide overflow detection.

Parallel streams can improve throughput on multi-core machines, but they add overhead for small streams. A rule of thumb is to use parallel() only when the stream has many elements and the per-element computation is nontrivial. For simple operations on a few hundred elements, sequential execution is usually faster.

LongStream vs Stream<Long>: When to Use Which

The choice between LongStream and Stream<Long> depends on the context. The table below summarizes the key differences.

AspectLongStreamStream<Long>
Element typelong (primitive)Long (object)
Memory footprintLower (no boxing)Higher (boxing overhead)
PerformanceBetter for large numeric pipelinesAcceptable for small data
CollectorsNot directly supportedFull Collector support
Optional resultsOptionalLongOptional<Long>
Best forNumeric computations, rangesWhen you need to collect or use complex collectors

Use LongStream when you are processing a large set of primitive longs and performance matters. Use Stream<Long> when you need to collect into a complex structure, use custom collectors, or when the data is already boxed and the volume is small.

A Practical Example: Processing Large Ranges

Suppose you need to find the sum of all even numbers from 1 to 10 million. Using LongStream, you can write a concise and efficient pipeline:

long sum = LongStream.rangeClosed(1, 10_000_000) .filter(n -> n % 2 == 0) .sum();

This avoids creating millions of Long objects. If you also need to compute statistics like average, you can use summaryStatistics():

LongSummaryStatistics stats = LongStream.rangeClosed(1, 10_000_000) .filter(n -> n % 2 == 0) .summaryStatistics(); System.out.println("Count: " + stats.getCount()); System.out.println("Sum: " + stats.getSum());

For parallel processing, simply add .parallel() before the terminal operation. This can significantly reduce runtime on machines with many cores, provided the operation is CPU-bound and the stream is large enough to justify the overhead.

long sumParallel = LongStream.rangeClosed(1, 10_000_000) .parallel() .filter(n -> n % 2 == 0) .sum();

When working with very large ranges, be mindful of the range vs rangeClosed boundary and the potential for overflow in intermediate calculations. These details matter more with primitive streams because there is no object wrapper to hide arithmetic issues.

java longstream: Practical Usage and Code Examples | RYUSLOG DEV