Skip to content

How to Limit a Kotlin Sequence Like Java Stream.limit

A developer on a technical forum recently asked: “In Java, I use Stream.limit(10) to truncate a stream. What is the idiomatic Kotlin Sequence equivalent for this behavior?”

While Kotlin was designed to feel familiar to Java developers, the naming conventions for collection and sequence operators often follow the “functional” naming style (common in Scala or Haskell) rather than the “descriptive” style used in the Java Stream API.

The direct equivalent of Java’s Stream.limit(n) in Kotlin is the take(n) function.

// Kotlin 1.9 (illustrative example)
val limitedSequence = mySequence.take(10)

Both Stream.limit(n) and Sequence.take(n) are intermediate operations and are lazy. They do not process the elements until a terminal operation (like toList() or forEach()) is called.


While take(n) is the standard solution, there are nuances regarding how it handles the underlying data and how it interacts with existing Java codebases.

In Kotlin, take(n) returns a sequence containing the first n elements. If the sequence contains fewer than n elements, it simply returns all elements without throwing an exception.

// Kotlin 1.9+
fun main() {
val numbers = generateSequence(1) { it + 1 } // Infinite sequence
val firstTen = numbers
.take(10)
.toList()
println(firstTen) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}

Why it works: The take operator internally tracks a counter. Once the counter reaches n, the iterator’s hasNext() returns false, effectively terminating the sequence processing early. This is identical to the short-circuiting behavior of Java’s limit().

2. Bridging Java Streams and Kotlin Sequences

Section titled “2. Bridging Java Streams and Kotlin Sequences”

If you are working in a polyglot codebase where a library returns a java.util.stream.Stream, you have two choices: use the Java method directly or convert it to a Kotlin Sequence.

// Java 8+ / Kotlin 1.9+ interop
import kotlin.streams.asSequence
val javaStream: java.util.stream.Stream<String> = getLegacyData()
// Option A: Continue using Java Streams
val limitedJava = javaStream.limit(5).toList()
// Option B: Convert to Kotlin Sequence for idiomatic processing
val limitedKotlin = javaStream.asSequence()
.take(5)
.map { it.uppercase() }
.toList()

Why choose one over the other?

  • Use Java Streams if you need parallel processing (Kotlin Sequences are strictly synchronous).
  • Use Kotlin Sequences if you want to use Kotlin-specific features like String extensions, filterIsInstance, or if you want to avoid the verbose syntax of Java Collectors.

A common mistake is using takeWhile when a hard limit is required.

Feature take(n) takeWhile { predicate }
Criteria Count-based (Long/Int) Condition-based (Boolean)
Termination Stops after N items Stops as soon as condition is false
Java Equivalent limit(n) takeWhile(predicate) (Java 9+)

Use take(n) when you know the exact maximum capacity required. Use takeWhile when you are consuming data until a specific state is reached (e.g., reading a file until an empty line).


  1. Avoid toList().take(n): Never convert a sequence to a list before taking elements. This loads the entire dataset into memory, defeating the purpose of a Sequence. Always call take(n) on the Sequence itself.
  2. Order Matters: Just like in Java, calling filter before take yields different results than calling it after. Ensure your take(n) is placed logically to capture the specific “top N” items you need.
  3. Check for Long vs Int: Java’s limit() accepts a long. Kotlin’s take() accepts an Int. If you are dealing with massive datasets exceeding 2^31 - 1 elements, you may need to implement a custom take or stick with Java Streams.
  4. Terminal Operations: Remember that a Sequence is “dead” until you call a terminal operation like count(), fold(), first(), or toList(). Simply calling take(10) does no work.

Does take(n) work on infinite sequences? Yes. Since take(n) is lazy and short-circuiting, it is the primary way to safely consume values from an infinite generator (like generateSequence) without causing an OutOfMemoryError.

What is the equivalent of Java’s skip(n)? The Kotlin equivalent of Stream.skip(long) is drop(n). It returns a sequence containing all elements except the first n elements.

Is there a performance difference between Java Streams and Kotlin Sequences? For small to medium sequential workloads, the difference is negligible. Java Streams have slightly more overhead due to the way they handle internal dispatching and potential parallelism. Kotlin Sequences are very lightweight wrappers around Iterator. However, for primitive types (like IntArray), Java’s IntStream can be faster as it avoids boxing/unboxing, whereas Kotlin Sequences will box primitives unless specialized libraries are used.