The Enhanced for Loop
Iterate arrays, lists, sets and map entries with for-each, and know why removing inside it throws ConcurrentModificationException.
for (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
nullthe loop throwsNullPointerException. Have repositories and services return an empty list instead ofnull. varworks for the loop variable:for (var entry : map.entrySet()).- Since Java 21 lists and other sequenced collections have
reversed(), sofor (var e : events.reversed())walks newest first without copying.
Example
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.
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.
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.
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