In Java, IntStream is the stream of primitive int values in the java.util.stream package, with factory methods such as range() and iterate() and numeric terminal operations such as sum(), average() and max(). It works on plain int values, so no Integer objects are created on the way.
We use IntStream to replace index-based for loops, to generate number sequences, to process the characters of a String and to compute totals and counts over int data.
The following example shows the operations this guide covers, from creating a stream to getting a result.
int[] upTo4 = IntStream.range(1, 5).toArray(); // [1, 2, 3, 4]
int[] upTo5 = IntStream.rangeClosed(1, 5).toArray(); // [1, 2, 3, 4, 5]
int[] evens = IntStream.iterate(0, i -> i <= 8, i -> i + 2).toArray(); // [0, 2, 4, 6, 8]
int total = IntStream.of(3, 5, 7).sum(); // 15
long bigOnes = IntStream.of(3, 50, 70).filter(i -> i > 10).count(); // 2
List<String> labels = IntStream.rangeClosed(1, 3).mapToObj(i -> "Q" + i).toList(); // [Q1, Q2, Q3]
The range() call excludes the end value while rangeClosed() includes it. We start with where IntStream fits, look at the ways to create one, replace index loops, transform values, compute results without overflow, and finish with a real use case and the cases where a plain loop is the better tool.
1. IntStream vs Stream vs a for Loop
The IntStream interface extends BaseStream, the same parent as Stream, but its methods take and return int values. That difference shows up in the method set. An IntStream has sum(), average() and range(), which a Stream<Integer> lacks, whereas collect(Collectors.toList()) works only on a Stream<Integer>.

The three options overlap, and each has a clear best use.
| Option | Best for | Watch out for |
|---|---|---|
| IntStream | Number ranges, totals, filtering and mapping int data | No break, and sum() can overflow |
| Stream<Integer> | Collecting into lists, sets and maps | Boxing cost and possible null elements |
| for loop | Early exit, checked exceptions, updating several variables | More code for filtering and collecting |
The other primitive streams, LongStream and DoubleStream, follow the same design, and primitive type streams compares all three. Moving between IntStream and Stream<Integer> is the topic of boxed streams.
2. Creating an IntStream in Java
The factory methods differ in whether the stream is finite and whether each value depends on the previous one. Infinite streams from generate() and the two-argument iterate() need a limit() or a takeWhile() before a terminal operation.
| Method | Produces | Example result |
|---|---|---|
| of(int…) | The given values | of(4, 2) gives 4, 2 |
| range(a, b) | a up to b – 1 | range(0, 3) gives 0, 1, 2 |
| rangeClosed(a, b) | a up to b | rangeClosed(1, 3) gives 1, 2, 3 |
| iterate(seed, next) | An infinite sequence | iterate(1, i -> i * 2) gives 1, 2, 4, … |
| iterate(seed, hasNext, next) | A finite sequence (Java 9) | Works like a for loop header |
| generate(supplier) | An infinite unordered stream | Repeated calls to the supplier |
| “text”.chars() | The char values of a String as int | “ab”.chars() gives 97, 98 |
| random.ints(n, from, to) | n random values | Random numbers in a range |
int[] powers = IntStream.iterate(1, i -> i * 2).limit(5).toArray(); // [1, 2, 4, 8, 16]
int[] fives = IntStream.generate(() -> 5).limit(3).toArray(); // [5, 5, 5]
int[] codes = "Hi".chars().toArray(); // [72, 105]
int[] fromArray = Arrays.stream(new int[] {9, 8, 7}, 1, 3).toArray(); // [8, 7]
int[] lengths = Stream.of("fig", "apple").mapToInt(String::length).toArray(); // [3, 5]
For random values, new Random(seed).ints() returns an IntStream too, and a fixed seed makes the sequence repeatable in tests. Generating random numbers covers the available generators.
int[] dice = new Random(42).ints(5, 1, 7).toArray();
boolean inRange = Arrays.stream(dice).allMatch(d -> d >= 1 && d <= 6); // true
3. Replacing Index-Based for Loops
A counting for loop maps to range() or to the three-argument iterate(), and the loop body becomes the stream operations. The benefit appears when the loop builds a result, because the stream returns it instead of changing a variable declared outside the loop.
List<String> fruits = List.of("apple", "banana", "cherry", "date");
List<String> everySecond = IntStream.range(0, fruits.size())
.filter(i -> i % 2 == 0)
.mapToObj(fruits::get)
.toList(); // [apple, cherry]
A stream has no reverse range(), so we count up and map each index to its mirror position, or use iterate() with a decrement. Since Java 21, List.reversed() is often simpler when the goal is only to read a list backwards.
int[] down = IntStream.range(0, 5).map(i -> 4 - i).toArray(); // [4, 3, 2, 1, 0]
int[] countdown = IntStream.iterate(10, i -> i > 0, i -> i - 3).toArray(); // [10, 7, 4, 1]
A cinema booking page prints the seat labels of a hall with rows A to C and four seats per row. Two nested loops become a flatMap(), where the outer range picks the row letter and the inner range numbers the seats.
List<String> seats = IntStream.rangeClosed('A', 'C')
.boxed()
.flatMap(row -> IntStream.rangeClosed(1, 4).mapToObj(n -> (char) row.intValue() + "" + n))
.toList();
int seatCount = seats.size(); // 12
String lastSeat = seats.getLast(); // "C4"
For looping over a list with its index as part of the result, iterating over a stream with indices shows range() next to the other options.
4. Filtering and Transforming int Values
The intermediate operations of IntStream mirror those of Stream, with names that say which type comes out. The map() method returns an int, mapToObj() returns any object, and mapToLong() and mapToDouble() switch to the other primitive streams.
int[] squares = IntStream.rangeClosed(1, 4).map(i -> i * i).toArray(); // [1, 4, 9, 16]
int[] unique = IntStream.of(3, 1, 3, 2, 1).distinct().sorted().toArray(); // [1, 2, 3]
double[] halves = IntStream.of(1, 3).mapToDouble(i -> i / 2.0).toArray(); // [0.5, 1.5]
String csv = IntStream.of(4, 8).mapToObj(String::valueOf).collect(Collectors.joining(",")); // "4,8"
The takeWhile() and dropWhile() methods, added in Java 9, stop or start at the first element that fails a condition. On sorted data, they act as a cut-off point, which filter() cannot do, because filter() always checks every element.
int[] belowTen = IntStream.of(2, 5, 9, 12, 3).takeWhile(i -> i < 10).toArray(); // [2, 5, 9]
int[] fromTen = IntStream.of(2, 5, 9, 12, 3).dropWhile(i -> i < 10).toArray(); // [12, 3]
5. sum(), average(), max() and the Overflow Trap
The numeric terminal operations return a plain int where a result always exists and an OptionalInt or OptionalDouble where an empty stream has no answer. The sum() method returns 0 for an empty stream, whereas max() and average() return an empty optional.
int sum = IntStream.of(4, 8, 15).sum(); // 27
OptionalInt max = IntStream.of(4, 8, 15).max(); // OptionalInt[15]
double avg = IntStream.of(4, 8, 15).average().orElse(0); // 9.0
double emptyAvg = IntStream.empty().average().orElse(0); // 0.0
int product = IntStream.rangeClosed(1, 5).reduce(1, (a, b) -> a * b); // 120
The sum() method adds with int arithmetic, so a large total wraps around to a negative number without any exception. For totals that can pass about 2.1 billion, such as bytes or cents, we call asLongStream() before sum(), or use Math.addExact() in a reduce() to fail fast.
int wrapped = IntStream.of(Integer.MAX_VALUE, 1).sum(); // -2147483648
long correct = IntStream.of(Integer.MAX_VALUE, 1).asLongStream().sum(); // 2147483648
int checked = IntStream.of(Integer.MAX_VALUE, 1).reduce(0, Math::addExact); // ArithmeticException: integer overflow
When we need several of these values at once, summaryStatistics() computes count, sum, min, max and average in one pass, as shown in the primitive streams article linked in section 1.
6. Converting an IntStream to an Array, a List or a String
An IntStream ends in an int[] with toArray(). A List<Integer> needs boxing first, because collections hold objects, and a String needs mapToObj() before joining.
int[] array = IntStream.range(0, 3).toArray(); // [0, 1, 2]
List<Integer> list = IntStream.range(0, 3).boxed().toList(); // [0, 1, 2]
String text = IntStream.range(0, 3).mapToObj(Integer::toString).collect(Collectors.joining("-")); // "0-1-2"
More array conversions are in stream toArray(), and joining options in joining a stream of strings.
7. Checking a Password Policy With chars()
A sign-up form requires passwords with at least 10 characters, one digit and one upper-case letter. The chars() method returns the characters as an IntStream, so Character methods that take an int work as predicates.
static boolean meetsPolicy(String password) {
return password.length() >= 10
&& password.chars().anyMatch(Character::isDigit)
&& password.chars().anyMatch(Character::isUpperCase);
}
boolean weak = meetsPolicy("applepie"); // false
boolean strong = meetsPolicy("AppleBanana7"); // true
long digits = "Order66Kiwi9".chars().filter(Character::isDigit).count(); // 3
Each chars() call creates a new stream, so calling it twice is fine. For text with emoji or other characters outside the Basic Multilingual Plane, codePoints() returns one value per character instead of two surrogate halves.
8. When a for Loop Is the Better Choice
An IntStream does not replace every loop. A plain loop reads better when the body changes several local variables, throws a checked exception, or must stop at a condition that depends on earlier iterations, because lambdas cannot change local variables and takeWhile() only sees the current element.
Speed is rarely the deciding factor for small ranges, so we choose by readability. For a hot loop in a performance-critical path, we measure both versions with JMH before changing it in either direction.
9. IntStream FAQs
Questions about reverse order, steps, characters and parallel ranges show up next to IntStream in search results.
9.1. How do we create an IntStream with a step other than 1?
Use the three-argument iterate(), which works like a for loop header.
int[] byFive = IntStream.iterate(0, i -> i <= 20, i -> i + 5).toArray(); // [0, 5, 10, 15, 20]
9.2. How do we iterate an IntStream in reverse order?
Map an ascending range to end – i, or use iterate() with a decrement, as in section 3. Sorting with boxed().sorted(Comparator.reverseOrder()) also works, but it creates objects and sorts values that are already in order.
9.3. Can an IntStream contain null?
No. Its elements are primitive int values, so there is no null. A Stream<Integer> with null elements throws NullPointerException in mapToInt(Integer::intValue), so filter the null values first.
9.4. Is IntStream.range() safe to use with parallel()?
Yes. A range knows its size and splits evenly, so IntStream.range(0, n).parallel() divides well across threads. The work per element must be large enough to pay for the thread coordination.
10. Conclusion
The IntStream interface handles primitive int data without boxing. We create it with range(), rangeClosed(), iterate(), chars() or mapToInt(), and finish with sum(), average(), max() or toArray().
Counting loops translate into ranges and flatMap(), and takeWhile() gives an early cut-off on ordered data. Two things need care. A large sum() overflows without an exception, so we switch to asLongStream(), and loops with checked exceptions or several changing variables stay as loops.
11. References
Happy Learning !!