Convert me a Java for-loop with break (find the first item over a threshold) into the equivalent EK9 stream pipeline.

← Control Flow Without break/continue/return · Ref: Q1239

EK9 has no break statement, so loops with early exit on first match are expressed as a stream pipeline using filter + head. The pipeline is more declarative AND eliminates the loop variable that Java needed.

THE JAVA PATTERN

  Integer firstBig = null;
  for (Integer n : numbers) {
    if (n > 50) {
      firstBig = n;
      break;
    }
  }
  if (firstBig != null) System.out.println(firstBig);

Three problems: a mutable accumulator, a break statement, and a null check. EK9 eliminates all three.

THE EK9 STREAM EQUIVALENT

Use filter to keep only matching items, then head to take the first one, then collect into a List:

  isBig() as pure
    -> n as Integer
    <- big as Boolean: n > 50
  firstBig <- cat numbers | filter by isBig | head 1 | collect as List of Integer

The pipeline reads top-to-bottom: 'cat' streams the list, 'filter' keeps matches, 'head 1' takes the first, 'collect' produces a final List. There is no loop variable, no mutable accumulator, no break.

USAGE

  numbers <- [10, 25, 60, 75, 90]
  firstBig <- cat numbers | filter by isBig | head 1 | collect as List of Integer
  if firstBig?
    stdout.println(`First > 50: ${firstBig}`)

WHY EK9 PREFERS THIS

1. No break — loop control is implicit in the pipeline.
2. No mutable accumulator — the result is an expression.
3. The filter predicate is reusable and unit-testable on its own.
4. Adding 'sort' or 'map' is one more pipe stage, not a refactor.

See Q284 for find-first via guarded assignment. See Q288 for migrating break loops. See Q142 for stream pipeline basics.

Example

defines module qa.without.streamfindfirst

  defines constant

    BIG_THRESHOLD <- 50

  defines function

    isBig() as pure
      -> n as Integer
      <- big as Boolean: n > BIG_THRESHOLD

  defines program

    MigrateBreakDemo()
      stdout <- Stdout()

      numbers <- [10, 25, 60, 75, 90]

      firstBig <- cat numbers | filter by isBig | head 1 | collect as List of Integer

      if firstBig?
        stdout.println(`First > ${BIG_THRESHOLD}: ${firstBig}`)

Common mistakes

E01070 — EK9 has no 'break' statement. Use a stream pipeline with 'filter | head 1' to take only the first match, eliminating the need for early loop exit. See ek9 -h E01070 for details.

Incorrect:

for n in numbers
        if n > 50
          firstBig: n
          break

Correct:

firstBig <- cat numbers | filter by isBig | head 1 | collect as List of Integer
Other ways to ask this
  • Migrate a Java for/break loop to EK9 without using break.
  • Show me how to replace 'for + break on first match' with an EK9 stream.
  • Rewrite a Java early-exit loop in idiomatic EK9.
  • How do I express 'find first over N' without a loop in EK9?

Coming from another language?

Java: for + break, or stream().filter().findFirst().orElse(null). Python: next((x for x in items if predicate(x)), None). Kotlin: items.firstOrNull { predicate(it) }. Rust: items.iter().find(|x| predicate(x)). Go: explicit for loop with break. EK9: 'cat items | filter by predicate | head 1 | collect as List of T' — explicit pipeline without break and without nullable return.

Keywords: migration, break, Java to EK9, stream, without break, head, filter, find first, early exit