The Enhanced for Loop

Iterate arrays, lists, sets and map entries with for-each, and know why removing inside it throws ConcurrentModificationException.

Syntaxfor (Type item : arrayOrIterable) { ... } for (var entry : map.entrySet()) { ... }

The enhanced for loop, often called for-each, visits every element of an array or of anything that implements Iterable: lists, sets, queues, and the views a map gives you.

for (Order order : orders) {
    total += order.totalCents();
}

Read the colon as "in". There is no index and no bounds to get wrong, which is why this is the default loop for most back-end code: totalling order lines, mapping entities to DTOs, validating each field of a request.

How it works

For an array the compiler generates an index loop. For an Iterable it calls iterator() and then hasNext() and next() on each pass. That detail explains the two big gotchas.

Do not change the collection while you iterate

Calling list.remove(x) or list.add(x) inside a for-each over the same ArrayList (or changing a HashMap while looping over one of its views) usually makes the iterator fail on its next step with ConcurrentModificationException. Worse, the change made before the exception has already happened, so the collection is left half-processed. And the exception is not guaranteed: removing the second-to-last element of an ArrayList ends the loop quietly, and the last element is never visited. Use list.removeIf(predicate), an explicit Iterator with iterator.remove(), or build a new list of the items you want to keep.

The loop variable is a copy of the reference

Assigning to the loop variable (tag = tag.strip()) changes the local variable only; the array or list is untouched. To replace elements, use an index loop, list.replaceAll(...), or a stream that produces a new list. Calling a method that mutates the object the variable points to does change it, because both refer to the same object.

Maps

A Map is not Iterable. Loop over map.entrySet() to get keys and values together, rather than looping over keySet() and calling get for each key. The order you see is the map's order: insertion order for LinkedHashMap, sorted for TreeMap, and unspecified for HashMap. Do not rely on HashMap order in a response body.

Other points

  • If the collection is null the loop throws NullPointerException. Have repositories and services return an empty list instead of null.
  • var works for the loop variable: for (var entry : map.entrySet()).
  • Since Java 21 lists and other sequenced collections have reversed(), so for (var e : events.reversed()) walks newest first without copying.

Example

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

When to use it

  • Total the lines of an order or cart before saving it.
  • Convert each entity returned by a repository into a response DTO.
  • Print or log per-endpoint counters from a map in a stable, sorted order.

More examples

ConcurrentModificationException and the fix

The iterator notices the list changed under it and fails on the next element, leaving the job half done. removeIf does the same work safely in one call.

Example · java
void main() {
    List<String> sessions = new ArrayList<>(
        List.of("s1:expired", "s2:active", "s3:expired", "s4:active"));

    try {
        for (String s : sessions) {
            if (s.endsWith(":expired")) {
                sessions.remove(s);
            }
        }
    } catch (ConcurrentModificationException e) {
        IO.println("caught ConcurrentModificationException");
    }
    // The first removal happened before the exception.
    IO.println(sessions);

    sessions.removeIf(s -> s.endsWith(":expired"));
    IO.println(sessions);
}
// Output:
// caught ConcurrentModificationException
// [s2:active, s3:expired, s4:active]
// [s2:active, s4:active]

Assigning to the loop variable changes nothing

The loop variable holds a copy of each reference, so reassigning it never writes back into the array. Normalising input in place needs the index, or a stream that builds a new collection.

Example · java
void main() {
    String[] tags = {" java ", "API", " Backend"};

    for (String tag : tags) {
        tag = tag.strip().toLowerCase(); // only the local variable changes
    }
    IO.println(Arrays.toString(tags));

    for (int i = 0; i < tags.length; i++) {
        tags[i] = tags[i].strip().toLowerCase();
    }
    IO.println(Arrays.toString(tags));
}
// Output:
// [ java , API,  Backend]
// [java, api, backend]

Reversed lists and sorted maps

reversed() (Java 21) returns a view, so walking newest first costs no copy. Copying into a TreeMap gives a stable, sorted order for reports and API output.

Example · java
void main() {
    List<String> events = new ArrayList<>(List.of("created", "paid", "shipped"));
    for (String event : events.reversed()) {
        IO.println(event);
    }

    // TreeMap iterates in key order, whatever order the entries came in.
    Map<String, Integer> hits = new TreeMap<>(Map.of("/orders", 42, "/health", 900, "/users", 17));
    for (Map.Entry<String, Integer> entry : hits.entrySet()) {
        IO.println(entry.getKey() + " " + entry.getValue());
    }
}
// Output:
// shipped
// paid
// created
// /health 900
// /orders 42
// /users 17

Discussion

  • Be the first to comment on this lesson.