Java Creating Streams: Learn Stream Sources and Stream Creation Methods

0

Creating Streams

Creating a stream is the first practical step toward using the Java Stream API. A stream does not hold data by itself; instead, it provides a pipeline for processing elements supplied by a source. Once you understand where streams come from, the rest of the Stream API becomes much easier to use.


Java gives you several ways to create streams. You can create them from collections, arrays, individual values, ranges of numbers, files, and even custom stream generators. The best approach depends on the kind of data you already have.

Why Are There Multiple Ways to Create Streams?

In real applications, data does not always arrive as a List. Sometimes you receive an array from an API, a set of unique values from a database operation, a sequence of numbers for calculations, or a file that needs to be processed line by line.


Java's Stream API therefore provides several stream creation techniques so that different data sources can enter the same processing pipeline.

Source Common Approach Typical Use
Collection collection.stream() Processing lists, sets, and other collections
Array Arrays.stream() Processing array elements
Individual values Stream.of() Creating a small stream directly
Numeric range IntStream.range() Processing sequences of integers
File Files.lines() Processing file contents line by line
Generated data Stream.generate() Creating potentially unbounded sequences
Iterated data Stream.iterate() Creating sequences based on repeated computation

Creating a Stream from a Collection

The most common way to create a stream is by calling stream() on a collection. Lists and sets are frequently used as stream sources.

import java.util.List;

public class CollectionStream {
    public static void main(String[] args) {

        List<String> languages = List.of(
            "Java",
            "Python",
            "Go",
            "JavaScript"
        );

        languages.stream()
                 .forEach(System.out::println);
    }
}

The list remains the data source, while stream() provides a sequential stream containing its elements. The terminal operation forEach() then consumes those elements.

Remember: Creating a stream does not copy the collection into a new collection. The stream provides a way to process the existing source.

Creating a Stream from a Set

A Set can also be converted into a stream using stream(). This is useful when the source already guarantees uniqueness.

import java.util.Set;

public class SetStream {
    public static void main(String[] args) {

        Set<String> technologies = Set.of(
            "Java",
            "Spring",
            "Hibernate",
            "Maven"
        );

        technologies.stream()
                    .forEach(System.out::println);
    }
}

The stream receives the elements from the set. The stream itself does not add or remove duplicate elements; it simply processes whatever elements the source provides.

Creating a Stream from an Array

Arrays do not have a stream() instance method like collections do. Instead, Java provides the Arrays.stream() utility method.

import java.util.Arrays;

public class ArrayStream {
    public static void main(String[] args) {

        String[] languages = {
            "Java",
            "Python",
            "C++",
            "Go"
        };

        Arrays.stream(languages)
              .forEach(System.out::println);
    }
}

The array is passed to Arrays.stream(), which creates a stream containing its elements.

Important: For object arrays, Arrays.stream(array) is a convenient way to enter the Stream API without first converting the array into a collection.

Creating a Stream from Part of an Array

Sometimes you do not want to process the entire array. Arrays.stream() also provides an overloaded form that accepts a starting index and an ending index.

import java.util.Arrays;

public class PartialArrayStream {
    public static void main(String[] args) {

        int[] numbers = {10, 20, 30, 40, 50};

        Arrays.stream(numbers, 1, 4)
              .forEach(System.out::println);
    }
}

The output contains 20, 30, and 40. The starting index is inclusive, while the ending index is exclusive.

Remember: The range follows the familiar Java pattern start inclusive, end exclusive.

Creating a Stream with Stream.of()

When you already have a few individual values, Stream.of() provides a concise way to create a stream.

import java.util.stream.Stream;

public class StreamOfExample {
    public static void main(String[] args) {

        Stream.of("Java", "Spring", "Docker")
              .forEach(System.out::println);
    }
}

This creates a stream containing three strings. It is particularly convenient for examples, tests, and small fixed sequences of values.

Creating a Stream from a Single Value

Stream.of() can also create a stream containing a single element.

Stream.of("Java")
      .forEach(System.out::println);

Although a one-element stream is not common in everyday application code, the capability is useful when a method needs to return a stream regardless of whether there is one value or several.

Creating an Empty Stream

Java also allows you to create an empty stream using Stream.empty().

import java.util.stream.Stream;

public class EmptyStream {
    public static void main(String[] args) {

        Stream<String> stream = Stream.empty();

        System.out.println(stream.count());
    }
}

The result is 0 because the stream contains no elements.

This is especially useful when a method needs to return a stream but has no data to provide. Returning an empty stream is often cleaner than returning null.

Important: Prefer returning an empty stream when "no elements" is a valid result. This allows callers to continue using stream operations without first checking for null.

Creating Streams with Numeric Ranges

Java provides specialized streams for primitive numbers. IntStream, LongStream, and DoubleStream are designed to process primitive numeric values efficiently.


For integer sequences, IntStream.range() is particularly useful.

import java.util.stream.IntStream;

public class RangeStream {
    public static void main(String[] args) {

        IntStream.range(1, 6)
                 .forEach(System.out::println);
    }
}

This prints the numbers from 1 through 5. The second argument, 6, is excluded.

range() vs rangeClosed()

A common source of confusion is the difference between range() and rangeClosed().

Method Start End Example Values
range() Inclusive Exclusive range(1, 5) 1, 2, 3, 4
rangeClosed() Inclusive Inclusive rangeClosed(1, 5) 1, 2, 3, 4, 5
IntStream.range(1, 5)
         .forEach(System.out::println);

IntStream.rangeClosed(1, 5)
         .forEach(System.out::println);

This distinction becomes particularly important when generating loop-like numeric sequences. If you need to include the final number, use rangeClosed().

Creating a Stream with Stream.generate()

Sometimes a stream is not based on a pre-existing collection or array. You may want Java to generate elements dynamically. Stream.generate() accepts a Supplier that produces values.

import java.util.stream.Stream;

public class GenerateStream {
    public static void main(String[] args) {

        Stream.generate(() -> "Hello")
              .limit(5)
              .forEach(System.out::println);
    }
}

The supplier can keep producing values, so this type of stream is potentially infinite. The limit() operation is therefore important when you want a finite number of generated elements.

Important: Be careful with potentially infinite streams. Always ensure that the pipeline has a sensible stopping condition when an operation can produce unlimited elements.

Creating a Stream with Stream.iterate()

Stream.iterate() creates elements by starting with an initial value and repeatedly applying a function to generate the next value.

import java.util.stream.Stream;

public class IterateStream {
    public static void main(String[] args) {

        Stream.iterate(1, number -> number + 1)
              .limit(5)
              .forEach(System.out::println);
    }
}

The first value is 1. Java then repeatedly applies number -> number + 1, producing 2, 3, and so on.

The Three-Argument iterate() Form

Modern Java also provides a three-argument form of iterate(). It accepts an initial value, a predicate that determines whether another element should be generated, and a function that calculates the next value.

import java.util.stream.Stream;

public class BoundedIterate {
    public static void main(String[] args) {

        Stream.iterate(
                1,
                number -> number <= 5,
                number -> number + 1
              )
              .forEach(System.out::println);
    }
}

This produces the values from 1 through 5 without requiring a separate limit() operation.

Creating a Stream from a File

Streams are also useful for processing text files. The Files.lines() method creates a stream whose elements represent lines from a file.

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;

public class FileStream {
    public static void main(String[] args) throws IOException {

        Path path = Path.of("data.txt");

        try (Stream<String> lines = Files.lines(path)) {
            lines.forEach(System.out::println);
        }
    }
}

The stream is connected to an external resource, so it should be closed after use. The try-with-resources statement is a clean way to ensure that the underlying resource is released.

Remember: Streams created from resources such as files require special care. Do not treat them exactly like an in-memory stream created from a list.

Creating a Parallel Stream

Collections can also create parallel streams using parallelStream().

import java.util.List;

public class ParallelStream {
    public static void main(String[] args) {

        List<Integer> numbers = List.of(
            10, 20, 30, 40, 50
        );

        numbers.parallelStream()
               .forEach(System.out::println);
    }
}

A parallel stream allows stream processing to be divided across multiple threads. However, parallel processing is not automatically better and should be used only when the workload benefits from it.

Collection Stream vs Parallel Stream

Method Processing Model Typical Starting Point
stream() Sequential Most ordinary stream processing
parallelStream() Potentially parallel Workloads that genuinely benefit from parallel execution

For most application code, start with a normal sequential stream. Move to parallel processing only after understanding the workload and confirming that parallel execution is appropriate.

Object Streams and Primitive Streams

Java distinguishes between object streams and specialized primitive streams. A stream of objects uses Stream<T>, while primitive numeric streams use IntStream, LongStream, and DoubleStream.

Stream Type Typical Data Example
Stream<T> Objects Stream<String>
IntStream int values IntStream.range(1, 10)
LongStream long values LongStream.of(10L, 20L)
DoubleStream double values DoubleStream.of(1.5, 2.5)

Primitive streams provide specialized operations for numeric processing and can avoid unnecessary boxing in appropriate situations.

A Practical Example

Suppose an application receives an array of employee salaries and needs to process only salaries above a certain threshold.

import java.util.Arrays;

public class SalaryStream {
    public static void main(String[] args) {

        double[] salaries = {
            32000.0,
            48000.0,
            75000.0,
            91000.0,
            42000.0
        };

        Arrays.stream(salaries)
              .filter(salary -> salary > 50000)
              .forEach(System.out::println);
    }
}

The important part is not simply that the array becomes a stream. Once the stream exists, the same pipeline concepts can be applied regardless of whether the original data came from a collection or an array.

Choosing the Right Stream Creation Method

A simple rule is to start from the source you already have. Do not convert one data structure into another merely to create a stream when Java already provides a direct stream creation method.

If You Have Prefer
List or Set collection.stream()
Object array Arrays.stream(array)
Primitive array Arrays.stream(array)
Several fixed values Stream.of()
Empty result Stream.empty()
Integer sequence IntStream.range() or rangeClosed()
Generated values Stream.generate()
Calculated sequence Stream.iterate()
File lines Files.lines()

Common Mistakes

  • Converting everything to a List first: If your source is already an array or collection, use the appropriate stream creation method directly.
  • Forgetting range boundaries: range() excludes its ending value, while rangeClosed() includes it.
  • Creating an infinite stream without a stopping condition: Use operations such as limit() or a bounded iteration condition when appropriate.
  • Reusing a consumed stream: A stream should normally be recreated from its source when another pipeline is needed.
  • Ignoring resource management: Streams connected to files or other external resources should be closed properly.
  • Using parallelStream() automatically: Parallelism introduces overhead and is not a universal performance improvement.

Best Practices

  • Create streams directly from the source whenever a suitable API exists.
  • Use Stream.of() for small, fixed groups of values.
  • Use primitive streams for appropriate numeric processing.
  • Use Stream.empty() instead of returning null when an empty stream represents a valid result.
  • Control generated or infinite streams carefully.
  • Close streams that own external resources.
  • Prefer sequential streams unless there is a clear reason to use parallel processing.

Interview Insights

Question: How can you create a stream from a collection?

Answer: Call the collection's stream() method. For example, list.stream().

Question: How do you create a stream from an array?

Answer: Use Arrays.stream(array). It works with object arrays and primitive arrays using the appropriate specialized stream type.

Question: What is the difference between IntStream.range() and IntStream.rangeClosed()?

Answer: range() uses an exclusive upper bound, while rangeClosed() includes the upper bound.

Question: What is the purpose of Stream.generate()?

Answer: It creates a stream whose elements are produced by a supplier. Because the supplier can continue producing values indefinitely, a limiting condition may be necessary.

Quick Revision

Concept Remember This
Collection Use collection.stream()
Array Use Arrays.stream()
Fixed values Use Stream.of()
Empty stream Use Stream.empty()
Integer range Use IntStream.range() or rangeClosed()
Generated sequence Use Stream.generate()
Calculated sequence Use Stream.iterate()
File lines Use Files.lines()
Parallel processing Use parallelStream() only when appropriate

Final Takeaway

Creating a stream is simply about choosing the right bridge between your data source and the Stream API. Collections commonly use stream(), arrays use Arrays.stream(), fixed values can use Stream.of(), numeric sequences can use specialized streams, and dynamic sequences can be created with generate() or iterate(). Once the stream has been created, all these sources can enter the same powerful pipeline of filtering, transformation, aggregation, and matching operations.

Post a Comment

0Comments
Post a Comment (0)