Java Stream takeWhile and dropWhile, iterate and ofNullable

Java Stream takeWhile and dropWhile explained with examples, the difference from filter() and limit(), unordered streams, iterate() and ofNullable().

Readings 3, 5, 8, 12, 4, 9 with the cut at 12; takeWhile keeps 3, 5, 8 and dropWhile keeps 12, 4, 9, while filter keeps every element below 10

The Java Stream methods takeWhile() and dropWhile() cut a stream at the first element that fails a condition. The method takeWhile() keeps the elements before that point, and dropWhile() keeps the element at that point and everything after it.

We use them on data that is already in order, such as sorted prices, log lines by time or a message whose header ends at the first blank line. They also stop infinite streams, together with the three-argument Stream.iterate() and Stream.ofNullable(), which came in the same release.

The following example runs both methods and filter() with the same condition on six sensor readings.

List<Integer> readings = List.of(3, 5, 8, 12, 4, 9);

List<Integer> taken = readings.stream().takeWhile(n -> n < 10).toList();     // [3, 5, 8]
List<Integer> dropped = readings.stream().dropWhile(n -> n < 10).toList();   // [12, 4, 9]
List<Integer> filtered = readings.stream().filter(n -> n < 10).toList();     // [3, 5, 8, 4, 9]

Notice that takeWhile() ignores the 4 and the 9 because it stops at 12, while filter() checks every element. We go through each method, compare them with filter() and limit(), look at unordered and parallel streams, and finish with Stream.iterate() with a condition and Stream.ofNullable().

1. Cutting a Stream at the First Failing Element

Both methods take a Predicate and test the elements in encounter order. The first element for which the predicate returns false is the cut point. On an ordered stream, the elements before the cut point form the longest prefix of elements that match the predicate.

Readings 3, 5, 8, 12, 4, 9 with the cut at 12; takeWhile keeps 3, 5, 8 and dropWhile keeps 12, 4, 9, while filter keeps every element below 10
The first element that fails the predicate splits the stream in two, and later matches such as 4 do not count

The two methods are complements of each other. For any ordered stream and predicate, the result of takeWhile() followed by the result of dropWhile() gives back the original elements.

MethodKeepsReads after the cut pointInfinite stream
takeWhile(p)the matching prefixNo, it stopsbecomes finite
dropWhile(p)everything from the first failing elementYes, it passes all of them onstays infinite
filter(p)every matching elementYes, it tests all of themstays infinite

2. takeWhile() Example

The method takeWhile() is short-circuiting, so it ends the stream as soon as the predicate fails. That makes it the natural way to stop an infinite stream at a value instead of after a fixed count.

List<Integer> powers = Stream.iterate(1, n -> n * 2)
        .takeWhile(n -> n < 100)
        .toList();
String powersShown = powers.toString();      // "[1, 2, 4, 8, 16, 32, 64]"

The predicate sees each element once. When it returns false for 128, the pipeline stops, and iterate() never computes 256.

On a sorted list, takeWhile() also saves work. For example, a price list sorted in ascending order lets us take every product under a budget and stop at the first one above it.

List<Integer> sortedPrices = List.of(5, 9, 14, 20, 35, 60);
List<Integer> affordable = sortedPrices.stream().takeWhile(p -> p <= 20).toList();   // [5, 9, 14, 20]

3. dropWhile() Example

The method dropWhile() skips elements while the predicate is true. After the first failing element, it stops testing and passes every remaining element on, including later elements that would match the predicate.

List<String> csv = List.of("# exported 2026-10-01", "# unit: EUR", "name,price", "tea,4", "# not a header");
List<String> data = csv.stream().dropWhile(line -> line.startsWith("#")).toList();
int dataLines = data.size();                 // 3
String firstData = data.getFirst();          // "name,price"

The last line starts with # as well, but dropWhile() keeps it because it comes after the cut point. If we want to remove every comment line, we need filter() instead.

Unlike takeWhile(), the method dropWhile() does not end a stream. On an infinite stream, it returns an infinite stream, so we still need limit() or takeWhile() after it.

List<Integer> fromTen = Stream.iterate(1, n -> n + 3)
        .dropWhile(n -> n < 10)
        .limit(3)
        .toList();
String fromTenShown = fromTen.toString();    // "[10, 13, 16]"

4. Compared With filter(), limit() and skip()

The methods takeWhile() and dropWhile() depend on the position of elements, while filter() depends only on the value of each element. When the data is sorted so that all matching elements come first, such as ascending prices with a “less than” condition, filter() and takeWhile() give the same result. On unsorted data they differ, as the intro example showed.

The methods limit() and skip() cut a stream at a position too, but the position is a fixed count, not a condition.

TaskMethodExample
Keep the first 3 elementslimit(3)page of results
Skip the first 3 elementsskip(3)skip a fixed header
Keep elements until a condition failstakeWhile(p)read until a blank line
Skip elements until a condition failsdropWhile(p)skip comment lines at the top
Keep all elements that matchfilter(p)remove every comment line

The primitive streams have the same methods. The types IntStream, LongStream and DoubleStream all declare takeWhile() and dropWhile() with a primitive predicate, so no boxing is needed.

int sumBelow50 = IntStream.iterate(1, n -> n + 7).takeWhile(n -> n < 50).sum();   // 154

5. Unordered and Parallel Streams

Both methods are defined by the encounter order. A stream from a List or an array is ordered, but a stream from a HashSet or Set.of() is not. For an unordered stream in which some but not all elements match, the operation is nondeterministic and may take or drop any subset of the matching elements, including none.

Set<Integer> codes = Set.of(1, 2, 3, 4, 5, 6, 7);
long unpredictable = codes.stream().takeWhile(n -> n < 5).count();   // depends on the Set iteration order
long below5 = codes.stream().sorted().takeWhile(n -> n < 5).count();  // 4

The first result can change between JDK builds or even between runs, because the iteration order of Set.of() is randomized per JVM run. A test that passes today proves nothing, so we call sorted() first, as in the second line, or use filter() when the order is not defined.

Parallel streams return correct results, because they keep the encounter order of an ordered source. On ordered parallel pipelines, takeWhile() and dropWhile() can be expensive, because the threads must agree on the longest prefix. If performance matters, we run these steps on a sequential stream.

6. Stream.iterate() With a hasNext Condition

The two-argument Stream.iterate(seed, next) creates an infinite stream. The three-argument version adds a hasNext predicate and ends the stream at the first value that fails it, like the condition of a for loop.

// for (int n = 1; n < 100; n *= 3) { ... }
List<Integer> triples = Stream.iterate(1, n -> n < 100, n -> n * 3).toList();   // [1, 3, 9, 27, 81]

The predicate also checks the seed, so a seed that fails the condition gives an empty stream. With pairs of numbers as the elements, the same call computes the Fibonacci numbers below 50.

List<Long> fibonacci = Stream.iterate(new long[] {0, 1}, f -> f[0] < 50, f -> new long[] {f[1], f[0] + f[1]})
        .map(f -> f[0])
        .toList();
String fibShown = fibonacci.toString();      // "[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]"
List<Integer> none = Stream.iterate(200, n -> n < 100, n -> n + 1).toList();     // []

The difference from iterate().takeWhile() is small. Both stop at the first failing value, but the three-argument form reads like a loop header and keeps the condition next to the seed.

7. Stream.ofNullable() for Values That May Be null

The method Stream.ofNullable(value) returns a stream with one element when the value is not null, and an empty stream when it is null. A single null is a legal stream element, but most stream steps call methods on their elements and would fail on it with a NullPointerException.

long one = Stream.ofNullable("jazz").count();     // 1
long zero = Stream.ofNullable(null).count();      // 0

The method is most useful inside flatMap(), where a lookup may return null. For example, Map.get() returns null for a missing key, and Stream.ofNullable() turns that into no elements instead of an exception.

Map<String, List<String>> playlists = Map.of(
        "focus", List.of("Rain", "Waves"),
        "party", List.of("Loud"));
List<String> keys = List.of("focus", "jazz", "party");

List<String> songs = keys.stream()
        .flatMap(key -> Stream.ofNullable(playlists.get(key)))
        .flatMap(List::stream)
        .toList();
String songsShown = songs.toString();          // "[Rain, Waves, Loud]"

For a single value, Optional offers the same idea with Optional.ofNullable(value).stream(). Inside a stream pipeline, Stream.ofNullable() is shorter.

8. Splitting a Message Into Headers and Body

Say an email client reads a raw message line by line. In the format of an email or an HTTP message, the headers come first, and the first empty line separates them from the body. That rule is about position, so it fits takeWhile() and dropWhile(), while filter() would also remove empty lines inside the body.

List<String> message = List.of(
        "From: ana@example.com",
        "Subject: Dinner",
        "",
        "Hi Ben,",
        "",
        "see you at 7.");

List<String> headers = message.stream().takeWhile(line -> !line.isEmpty()).toList();
List<String> body = message.stream().dropWhile(line -> !line.isEmpty()).skip(1).toList();

int headerCount = headers.size();      // 2
int bodyLines = body.size();           // 3
String greeting = body.getFirst();     // "Hi Ben,"

The skip(1) call removes the separator line, which dropWhile() keeps because it is the first failing element. The empty line inside the body survives, because dropWhile() stops testing after the cut point.

9. takeWhile and dropWhile FAQs

The Java version and the short-circuiting rule cause most of the confusion around the two methods, followed by edge cases such as an empty result.

9.1. Which Java version added takeWhile() and dropWhile()?

Java 9 added takeWhile(), dropWhile(), the three-argument iterate() and ofNullable() to the Stream interface. They work unchanged in Java 25, and the Java features by version page lists the later stream additions.

9.2. Is dropWhile() short-circuiting?

No. Only takeWhile() is short-circuiting. The method dropWhile() stops testing the predicate after the cut point, but it still passes every remaining element downstream.

9.3. What if no element matches the predicate?

The method takeWhile() returns an empty stream and dropWhile() returns all elements. If every element matches, it is the other way around.

9.4. How do we get takeWhile() on Java 8?

Java 8 has no built-in equivalent. The usual options are a loop with break, or a custom Spliterator that wraps the source and stops at the first failing element.

10. Conclusion

The method takeWhile() keeps the leading elements that match a predicate and stops at the first one that does not. The method dropWhile() skips those leading elements and returns the rest, without testing them again.

Both depend on encounter order, so they belong on lists, sorted data and infinite generated streams, not on sets. Together with the three-argument Stream.iterate() and Stream.ofNullable(), they replace many loops with stream code that states the stop condition in one place.

11. References

Happy Learning !!

Source Code on Github

Leave a Comment

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.