forEach()

Run an action for every key and value with a lambda, and know when a plain loop or replaceAll is the better choice.

Signature
default void forEach(BiConsumer<? super K, ? super V> action)
  • Added in Java Java 8

Parameters

ParameterTypeDescription
$actionBiConsumer<? super K, ? super V>Called with each key and its value.

Return value

voidNothing. The action is called once for each entry, in the map's iteration order.

Errors

NullPointerException if the action is null; ConcurrentModificationException if the map is structurally modified during the call (HashMap, LinkedHashMap, TreeMap); any runtime exception thrown by the action propagates to the caller.

forEach takes a BiConsumer and calls it with every key and value: headers.forEach((name, value) -> ...). It is the shortest way to visit a map when the body is a single statement, such as logging or copying entries somewhere else. It was added to Map in Java 8, and entries are visited in the map's iteration order.

What a lambda cannot do

  • Stop early. There is no break. A return inside the lambda only ends the call for the current entry, and the next entry runs anyway. If you want the first match, use a loop over entrySet() or a stream with filter and findFirst.
  • Throw checked exceptions. BiConsumer.accept declares none, so writing to a Writer or OutputStream that throws IOException does not compile without a try/catch inside the lambda. A plain loop lets the method declare throws IOException and keeps the real error.
  • Assign local variables. Locals used in a lambda must be effectively final, so a running total needs a loop, an AtomicInteger, or better, a stream with sum().

Changing the map

Adding or removing entries from inside the action throws ConcurrentModificationException on the common map types. To change every value, use replaceAll((k, v) -> newValue), also added in Java 8. To remove matching entries, use entrySet().removeIf(...).

ConcurrentHashMap

On a ConcurrentHashMap, forEach is weakly consistent: it never throws ConcurrentModificationException, and it may or may not see updates made by other threads while it runs. That is fine for a metrics snapshot, but the result is not a consistent point-in-time view. ConcurrentHashMap also has a forEach(parallelismThreshold, action) overload that can split the work across threads.

forEach or a loop?

Both visit each entry once. Pick forEach for short side effects, a loop when you need break, checked exceptions or local state. Do not use forEach to build another collection; a stream with a collector says what you mean and handles the details.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Copying a map of default headers onto every outgoing HTTP response.
  • Logging each configuration key and value at startup, with secrets masked.
  • Exporting per-endpoint counters from a ConcurrentHashMap to a metrics system.

Frequently asked questions

How do I break out of Map.forEach?

You cannot. return inside the lambda only skips the current entry. Use a for loop over entrySet() with break, or stream the entries and use filter and findFirst.

Can I modify a map inside forEach?

Not its structure: adding or removing keys throws ConcurrentModificationException on HashMap, LinkedHashMap and TreeMap. Use replaceAll to change values and entrySet().removeIf to remove entries.

Is Map.forEach faster than a for loop over entrySet?

Not in any way you should design around; both visit each entry once. Choose by readability and by whether you need break, checked exceptions or local variables.

More examples

Cleaning every value with replaceAll

replaceAll changes values in place without touching the map's structure, so it is safe where calling put inside forEach would not be. The brackets make the stripped spaces visible.

Example · java
void main() {
    Map<String, String> form = new LinkedHashMap<>();
    form.put("email", "  [email protected] ");
    form.put("name", " Ada Lovelace");

    form.replaceAll((field, value) -> field.equals("email")
            ? value.strip().toLowerCase()
            : value.strip());

    form.forEach((field, value) -> IO.println(field + "=[" + value + "]"));
}
// Output:
// email=[[email protected]]
// name=[Ada Lovelace]

Checked exceptions need a plain loop

The loop lets writeHeaders declare IOException and pass a real write failure to its caller. Inside forEach you would have to catch it and wrap it in an unchecked exception.

Example · java
void writeHeaders(Map<String, String> headers, Writer out) throws IOException {
    // headers.forEach((k, v) -> out.write(k + ": " + v)); does not compile:
    // BiConsumer.accept cannot throw IOException.
    for (Map.Entry<String, String> e : headers.entrySet()) {
        out.write(e.getKey() + ": " + e.getValue() + "\n");
    }
}

void main() throws IOException {
    Map<String, String> headers = new LinkedHashMap<>();
    headers.put("Content-Type", "text/plain");
    headers.put("Content-Length", "11");

    StringWriter out = new StringWriter();
    writeHeaders(headers, out);
    IO.print(out);
}
// Output:
// Content-Type: text/plain
// Content-Length: 11

forEach cannot stop early

forEach visited all four services even though the answer was known at the third. The loop stops as soon as it finds the slow one, and its counter is an ordinary local variable.

Example · java
void main() {
    Map<String, Integer> latencyMs = new LinkedHashMap<>();
    latencyMs.put("db", 12);
    latencyMs.put("cache", 2);
    latencyMs.put("payments", 950);
    latencyMs.put("search", 40);

    AtomicInteger calls = new AtomicInteger();
    latencyMs.forEach((service, ms) -> {
        calls.incrementAndGet();
        if (ms < 500) {
            return; // skips this entry only
        }
        IO.println("slow: " + service);
    });
    IO.println("forEach calls: " + calls.get());

    int checked = 0;
    for (var e : latencyMs.entrySet()) {
        checked++;
        if (e.getValue() >= 500) {
            IO.println("first slow: " + e.getKey() + " after " + checked + " checks");
            break;
        }
    }
}
// Output:
// slow: payments
// forEach calls: 4
// first slow: payments after 3 checks

Discussion

  • Be the first to comment on this lesson.