Skip to content
Java java8 6 min read

forEach() Method

The forEach() method, introduced in Java 8, gives you a clean, readable way to iterate over collections and streams without writing a traditional for loop. It pairs naturally with lambda expressions and method references to produce expressive, concise code.

Why forEach()?

Before Java 8, iterating a collection meant writing a for loop or using an Iterator. Both approaches are fine, but they carry boilerplate. forEach() lets you focus on what you want to do with each element rather than the mechanics of traversal.

import java.util.List;

public class ForEachBasic {
    public static void main(String[] args) {
        List<String> fruits = List.of("Apple", "Banana", "Cherry");

        // Traditional for-each loop
        for (String fruit : fruits) {
            System.out.println(fruit);
        }

        // Java 8 forEach() — same result, less noise
        fruits.forEach(fruit -> System.out.println(fruit));
    }
}

Output:

Apple
Banana
Cherry

Where Does forEach() Come From?

forEach() is defined in two places:

SourceSignatureWho uses it
java.lang.Iterabledefault void forEach(Consumer<? super T> action)All Collection types (List, Set, Map values, etc.)
java.util.stream.Streamvoid forEach(Consumer<? super T> action)Streams

Because it is a default method on Iterable (see Default Methods), every existing collection automatically gained it without any changes to those classes.

The argument is a Consumer<T> — a functional interface with a single method accept(T t) that takes a value and returns nothing.

forEach() on Collections

List

import java.util.List;

public class ListForEach {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5);

        numbers.forEach(n -> System.out.println("Square: " + (n * n)));
    }
}

Output:

Square: 1
Square: 4
Square: 9
Square: 16
Square: 25

Set

import java.util.Set;

public class SetForEach {
    public static void main(String[] args) {
        Set<String> languages = Set.of("Java", "Python", "Kotlin");

        languages.forEach(lang -> System.out.println(lang.toUpperCase()));
    }
}

Note: Set has no guaranteed order, so the output order may vary between runs.

Map

Map does not implement Iterable, but it has its own forEach() that takes a BiConsumer<K, V> — a functional interface that accepts two arguments.

import java.util.Map;

public class MapForEach {
    public static void main(String[] args) {
        Map<String, Integer> scores = Map.of("Alice", 95, "Bob", 82, "Carol", 88);

        scores.forEach((name, score) ->
            System.out.println(name + " scored " + score));
    }
}

Output:

Alice scored 95
Bob scored 82
Carol scored 88

(Order may vary since Map.of() returns an unordered map.)

forEach() on Streams

When combined with the Stream API, forEach() acts as a terminal operation — it triggers the pipeline and consumes each element.

import java.util.List;

public class StreamForEach {
    public static void main(String[] args) {
        List<String> names = List.of("alice", "bob", "charlie", "dave");

        names.stream()
             .filter(name -> name.length() > 3)
             .map(String::toUpperCase)
             .forEach(System.out::println);
    }
}

Output:

ALICE
CHARLIE
DAVE

Tip: Prefer forEach() at the end of a stream pipeline for side effects like printing. For transformations, use map(), filter(), and collect() instead of forEach with mutation.

Using Method References

Anywhere you pass a lambda that just calls one method, you can replace it with a method reference. This is a common shorthand with forEach().

import java.util.List;

public class MethodRefForEach {
    public static void main(String[] args) {
        List<String> cities = List.of("Tokyo", "Paris", "Cairo");

        // Lambda
        cities.forEach(city -> System.out.println(city));

        // Equivalent method reference — cleaner!
        cities.forEach(System.out::println);
    }
}

Both lines produce the same output. Use whichever reads more clearly.

forEach() vs Traditional Loops

FeatureTraditional for-each loopforEach() method
SyntaxVerboseConcise (lambda / method ref)
Works with streamsNoYes
Break / continueYesNo — use return inside lambda to skip one element
Exception handlingChecked exceptions allowedChecked exceptions must be wrapped
Parallel executionNo (without extra code)Use parallelStream().forEach()
Order guaranteeYesYes for sequential; not guaranteed for parallel

Warning: You cannot use break or continue inside a forEach() lambda. If you need early exit, use a traditional loop or Stream.anyMatch() / Stream.takeWhile().

Skipping Elements (Simulating continue)

Inside a forEach() lambda, a return statement skips to the next element — it behaves like continue in a loop.

import java.util.List;

public class SkipInForEach {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);

        // Print only odd numbers
        numbers.forEach(n -> {
            if (n % 2 == 0) return; // skip even numbers
            System.out.println(n);
        });
    }
}

Output:

1
3
5

Checked Exceptions Inside forEach()

Consumer.accept() does not declare checked exceptions, so you cannot throw one directly. You have two common workarounds.

import java.util.List;

public class ForEachException {
    // Wrap the checked exception in a RuntimeException
    public static void main(String[] args) {
        List<String> filenames = List.of("a.txt", "b.txt");

        filenames.forEach(file -> {
            try {
                processFile(file);
            } catch (Exception e) {
                throw new RuntimeException("Failed for " + file, e);
            }
        });
    }

    static void processFile(String name) throws Exception {
        System.out.println("Processing " + name);
    }
}

Output:

Processing a.txt
Processing b.txt

Tip: For cleaner code, extract the try/catch into a helper method that wraps checked exceptions, then pass that helper as a method reference.

Parallel forEach

For large datasets, you can use parallelStream() with forEach() to process elements concurrently. Be careful: the order of execution is not guaranteed.

import java.util.List;

public class ParallelForEach {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5);

        numbers.parallelStream()
               .forEach(n -> System.out.println(Thread.currentThread().getName() + ": " + n));
    }
}

Output (example — order varies):

main: 3
ForkJoinPool.commonPool-worker-1: 1
ForkJoinPool.commonPool-worker-2: 5
main: 4
ForkJoinPool.commonPool-worker-1: 2

Warning: Only use parallelStream() when the operation is stateless and thread-safe. Avoid shared mutable state inside a parallel forEach() — it leads to race conditions.

Under the Hood

When you call forEach() on a standard ArrayList, the JVM calls the overridden implementation in ArrayList itself (not the default from Iterable). ArrayList.forEach() uses a plain index-based loop internally — it avoids creating an Iterator object, which saves a small allocation.

// Pseudocode for ArrayList.forEach
for (int i = 0; i < size; i++) {
    action.accept(elementData[i]);
}

The Consumer<T> lambda you pass is compiled into a synthetic class (or a method handle via invokedynamic). At JIT time, the JVM often inlines both the lambda body and the loop, producing machine code nearly identical to a handwritten loop — so there’s no performance penalty for using forEach() in the common case.

For streams, forEach() is a terminal operation that drives the lazy pipeline. Each upstream stage (filter, map, etc.) is fused together during execution to avoid creating intermediate collections. This is what makes stream pipelines memory-efficient.

Last updated June 13, 2026
Was this helpful?