> ## Content Index
> Fetch the complete content index at: https://www.ggorantala.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# How to Iterate Over a Map in Java: entrySet and forEach
- URL: https://www.ggorantala.dev/how-to-iterate-map-java/
- Published: 2026-08-22T05:01:00.000Z
- Updated: 2026-10-31T06:34:30.000Z
- Description: Loop over a Java Map the right way: entrySet, forEach, iterator removal, sorted output and the exceptions to expect, built on a small currency-rates feed.
- Author: Gopi Gorantala
- Tags: how-to-tutorials, java-collections, java-tutorials

## Start with the printed rates

This is what the finished program prints when it walks a currency-rates map five different ways, edits it while walking, and then breaks it on purpose:

```log
-- entrySet() for-each
EUR -> 0.9215
GBP -> 0.7890
JPY -> 149.82
INR -> 83.27
-- iterator remove (drop rates above 100)
{EUR=0.9215, GBP=0.7890, INR=83.27}
-- setValue on entry
{EUR=0.930715, INR=84.1027, CHF=0.890012}
-- CME trap
CME: java.util.ConcurrentModificationException

```

If you want to know **how to iterate over a map in Java**, the short answer is `for (Map.Entry<K, V> e : map.entrySet())`, or `map.forEach((k, v) -> ...)` when you don't need to remove anything. The rest of this article is about the cases where that answer isn't enough: removing while looping, changing values, getting sorted output, and the three or four ways a loop over a map throws at you.

The example is a currency-rates feed: currency codes as keys, `BigDecimal` rates as values. It's one class, one checks file, and no build tool. I did it on plain `javac` because Maven Central wasn't reachable from my build box this run, so there's no JUnit here; the checks are a small hand-written runner you can paste in and later convert to JUnit.

Budget about 25 minutes. The build took me less than that; most of the time went into working out why one of my "this should throw" examples didn't (it's in the troubleshooting section, and it's a good one).

## Versions I used

| Tool           | Version I used                   | Check it with          |
| -------------- | -------------------------------- | ---------------------- |
| JDK            | OpenJDK 21.0.12.1 (Ubuntu build) | java -version          |
| Compiler flags | \--release 21 -Xlint:all         | javac --release 21 ... |
| Build tool     | none                             |                        |

`Main` needs Java 10 or newer (it uses `var`), and `Checks` uses Java 21's `SequencedMap`. I compiled `Main` with `--release 17` as well and it was clean. You should know what a `Map` is and how generics read; nothing else.

Need a JDK? On macOS or Linux, `sdk install java 21.0.12-tem` through SDKMAN works (check `sdk list java` for the exact identifier on your machine; I didn't run this one).

## Create the project

I'd use the command line here, because it's two files. Then IntelliJ IDEA and Eclipse are a minute each.

```bash
mkdir -p rates/src/com/example/rates && cd rates

```

The tree when you're done:

```log
rates/
└── src/com/example/rates/
    ├── Main.java
    └── Checks.java

```

**In IntelliJ IDEA** (not executed here, from the vendor docs): File → New → Project, choose Java, pick JDK 21, untick the sample-code box, and name it `rates`. Create the package `com.example.rates` under `src` and paste the files in.

**In Eclipse** (docs only): File → New → Java Project, select a JRE of 21, then right-click `src` → New → Package → `com.example.rates`. Paste the files in; Run As → Java Application.

## How to iterate over a Map in Java, one pattern at a time

Every step edits the same `Main.java`. The complete file is below; I'll then walk through which part answers which question.

### Step 1: Write the whole program

`src/com/example/rates/Main.java`

```java
package com.example.rates;

import java.math.BigDecimal;
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Map<String, BigDecimal> rates = new LinkedHashMap<>();
        rates.put("EUR", new BigDecimal("0.9215"));
        rates.put("GBP", new BigDecimal("0.7890"));
        rates.put("JPY", new BigDecimal("149.82"));
        rates.put("INR", new BigDecimal("83.27"));

        System.out.println("-- entrySet() for-each");
        for (Map.Entry<String, BigDecimal> e : rates.entrySet()) {
            System.out.println(e.getKey() + " -> " + e.getValue());
        }
        System.out.println("-- Map.forEach");
        rates.forEach((code, rate) -> System.out.println(code + " -> " + rate));
        System.out.println("-- keySet() then get()");
        for (String code : rates.keySet()) {
            System.out.println(code + " -> " + rates.get(code));
        }
        System.out.println("-- values()");
        BigDecimal sum = BigDecimal.ZERO;
        for (BigDecimal r : rates.values()) sum = sum.add(r);
        System.out.println("sum = " + sum);

        System.out.println("-- iterator remove (drop rates above 100)");
        for (Iterator<Map.Entry<String, BigDecimal>> it = rates.entrySet().iterator(); it.hasNext(); ) {
            if (it.next().getValue().compareTo(new BigDecimal("100")) > 0) it.remove();
        }
        System.out.println(rates);

        System.out.println("-- removeIf on entrySet");
        rates.put("CHF", new BigDecimal("0.8812"));
        rates.entrySet().removeIf(e -> e.getKey().equals("GBP"));
        System.out.println(rates);

        System.out.println("-- setValue on entry");
        for (Map.Entry<String, BigDecimal> e : rates.entrySet()) {
            e.setValue(e.getValue().multiply(new BigDecimal("1.01")));
        }
        System.out.println(rates);

        System.out.println("-- replaceAll");
        rates.replaceAll((k, v) -> v.setScale(2, java.math.RoundingMode.HALF_UP));
        System.out.println(rates);

        System.out.println("-- sorted by value, streams");
        rates.entrySet().stream()
             .sorted(Map.Entry.comparingByValue())
             .forEach(e -> System.out.println(e.getKey() + " " + e.getValue()));

        System.out.println("-- TreeMap sorted keys");
        new TreeMap<>(rates).forEach((k, v) -> System.out.println(k + " " + v));

        System.out.println("-- HashMap order is not insertion order");
        Map<String, Integer> h = new HashMap<>();
        for (String s : List.of("USD", "EUR", "GBP", "JPY", "INR", "CHF", "AUD")) h.put(s, s.length());
        System.out.println(h.keySet());

        System.out.println("-- CME trap");
        try {
            for (String k : rates.keySet()) if (k.equals("EUR")) rates.remove(k);
            System.out.println("no CME: " + rates);
        } catch (ConcurrentModificationException ex) { System.out.println("CME: " + ex); }
        Map<String, Integer> m = new LinkedHashMap<>(Map.of("a", 1));
        m.put("b", 2); m.put("c", 3);
        try {
            for (String k : m.keySet()) if (k.equals("a")) m.remove(k);
        } catch (ConcurrentModificationException ex) { System.out.println("CME: " + ex); }
        try {
            for (String k : m.keySet()) m.put(k + k, 0);
        } catch (ConcurrentModificationException ex) { System.out.println("CME on put: " + ex); }

        System.out.println("-- immutable Map.of entry setValue");
        try {
            for (var e : Map.of("x", 1).entrySet()) e.setValue(2);
        } catch (UnsupportedOperationException ex) { System.out.println("UOE: " + ex); }

        System.out.println("-- null value / entry kept past loop");
        Map<String,Integer> hm = new HashMap<>(); hm.put("k", 1);
        Map.Entry<String,Integer> kept = null;
        for (var e : hm.entrySet()) kept = e;
        hm.put("k", 99);
        System.out.println("entry after put: " + kept);
        Map.Entry<String,Integer> copy = Map.entry("k", 1);
        System.out.println("immutable copy: " + copy + " (use Map.entry or new AbstractMap.SimpleEntry to keep one)");
    }
}

```

Compile and run:

```bash
mkdir -p out
javac --release 21 -Xlint:all -d out src/com/example/rates/*.java
java -cp out com.example.rates.Main

```

On Windows PowerShell the `*.java` glob can be refused by `javac`; list the two files by name.

**Checkpoint:** the compile prints nothing (that's the point of `-Xlint:all`). The run starts with `-- entrySet() for-each` and the four rates in insertion order.

### Step 2: Know which loop to reach for

Three loops give you the same output on the first run, so it's fair to ask why there are three.

- `entrySet()` hands you a key and its value together. One pass over the table. This is my default.
- `keySet()` plus `get()` looks the key up again on every turn. It works, and it's what most older tutorials teach. On a `HashMap` each `get` is a second hash lookup.
- `values()` is for when you don't need the key at all (the sum in the program).

Is the difference real? On a two-million-entry `HashMap<Integer, Integer>`, on my box, three rounds:

```log
keySet+get 78 ms | entrySet 40 ms
keySet+get 68 ms | entrySet 16 ms
keySet+get 44 ms | entrySet 15 ms

```

That's an unscientific timing (no JMH, JIT still warming up), so read it as "entrySet isn't slower, and usually isn't close". For a map with twenty entries you'll never notice. I still write `entrySet()` because I don't want to think about it later.

### Step 3: Use forEach when you only read

`map.forEach((code, rate) -> ...)` takes a two-argument lambda. It's the shortest correct loop and the output above shows it matches `entrySet()` exactly. Pass one parameter by mistake and you get this:

```log
C.java:2: error: incompatible types: incompatible parameter types in lambda expression

```

Don't modify the map inside the lambda either. My test, which puts a new key from inside `forEach`:

```log
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.HashMap.forEach(HashMap.java:1432)

```

### Step 4: Remove while looping — with the iterator, or removeIf

Calling `map.remove(k)` inside a for-each loop is the classic mistake. Two safe versions are in the program:

```java
for (Iterator<Map.Entry<String, BigDecimal>> it = rates.entrySet().iterator(); it.hasNext(); ) {
    if (it.next().getValue().compareTo(new BigDecimal("100")) > 0) it.remove();
}

```

and the shorter `rates.entrySet().removeIf(e -> e.getKey().equals("GBP"))`. Both write through to the map. Prefer `removeIf` unless you need the iterator for something else.

**Checkpoint:** after the iterator version, JPY is gone: `{EUR=0.9215, GBP=0.7890, INR=83.27}`. After `removeIf`, so is GBP and the new CHF entry is last: `{EUR=0.9215, INR=83.27, CHF=0.8812}`.

### Step 5: Change values in place

You can call `setValue` on an entry that comes out of a `HashMap`, `LinkedHashMap` or `TreeMap`'s `entrySet()`, and the map sees it. Or skip the loop with `replaceAll`:

```log
-- setValue on entry
{EUR=0.930715, INR=84.1027, CHF=0.890012}
-- replaceAll
{EUR=0.93, INR=84.10, CHF=0.89}

```

Changing a *value* during iteration is fine. Adding or removing a *key* is the problem, and that's next.

### Step 6: Get sorted output

A `HashMap` doesn't promise any order. Mine printed `[CHF, AUD, JPY, EUR, GBP, USD, INR]` for keys I inserted as USD, EUR, GBP, JPY, INR, CHF, AUD. Don't rely on yours matching.

- Insertion order: use a `LinkedHashMap` (see [how to use LinkedHashMap](https://ggorantala.dev/how-to-use-linkedhashmap-java/?ref=ggorantala.dev)).
- Sorted by key: wrap in `new TreeMap<>(map)`. `TreeMap` rejects null keys with a `NullPointerException`; `HashMap` takes one.
- Sorted by value: stream the `entrySet()` through `Map.Entry.comparingByValue()`. To keep that order in a map, collect into a `LinkedHashMap`, otherwise you've re-hashed it ([collecting stream results](https://ggorantala.dev/how-to-collect-stream-results-java/?ref=ggorantala.dev), and [Comparable and Comparator](https://ggorantala.dev/how-to-implement-comparable-comparator-java/?ref=ggorantala.dev) for custom ordering).

## Check it works end to end

`src/com/example/rates/Checks.java`

```java
package com.example.rates;

import java.util.*;
import java.util.stream.*;

public class Checks {
    static int passed, failed;
    static void check(String name, boolean ok) {
        if (ok) passed++; else { failed++; System.out.println("FAIL: " + name); }
    }
    static <T extends Throwable> void throwsA(String name, Class<T> type, Runnable r) {
        try { r.run(); check(name, false); }
        catch (Throwable t) { check(name, type.isInstance(t)); }
    }
    public static void main(String[] args) {
        Map<String,Integer> m = new LinkedHashMap<>();
        m.put("EUR", 1); m.put("GBP", 2); m.put("JPY", 3);

        check("entrySet order", m.entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toList()).equals(List.of("EUR","GBP","JPY")));
        List<String> seen = new ArrayList<>();
        m.forEach((k, v) -> seen.add(k + v));
        check("forEach pairs", seen.equals(List.of("EUR1","GBP2","JPY3")));

        for (Map.Entry<String,Integer> e : m.entrySet()) e.setValue(e.getValue() * 10);
        check("setValue writes through", m.get("GBP") == 20);

        Iterator<Map.Entry<String,Integer>> it = m.entrySet().iterator();
        while (it.hasNext()) if (it.next().getKey().equals("GBP")) it.remove();
        check("iterator remove", !m.containsKey("GBP") && m.size() == 2);

        m.put("GBP", 2);
        m.entrySet().removeIf(e -> e.getValue() > 15);
        check("removeIf", m.keySet().equals(Set.of("EUR","GBP")));

        throwsA("remove in for-each", ConcurrentModificationException.class, () -> {
            Map<String,Integer> t = new HashMap<>(Map.of("a",1,"b",2,"c",3));
            for (String k : t.keySet()) t.remove(k);
        });
        throwsA("put new key in for-each", ConcurrentModificationException.class, () -> {
            Map<String,Integer> t = new LinkedHashMap<>();
            t.put("a", 1); t.put("b", 2);
            for (String k : t.keySet()) t.put(k + "x", 0);
        });
        Map<String,Integer> one = new LinkedHashMap<>(Map.of("a",1));
        for (String k : one.keySet()) one.put(k + "x", 0);
        check("one-entry map: put new key does NOT throw", one.size() == 2);
        Map<String,Integer> same = new HashMap<>(Map.of("a",1));
        for (String k : same.keySet()) same.put(k, 5);
        check("put existing key is fine", same.get("a") == 5);

        throwsA("Map.of entry immutable", UnsupportedOperationException.class, () -> {
            for (var e : Map.of("x",1).entrySet()) e.setValue(2);
        });
        throwsA("Map.entry immutable", UnsupportedOperationException.class, () -> Map.entry("k",1).setValue(2));

        Map<String,Integer> tm = new TreeMap<>(Comparator.reverseOrder());
        tm.putAll(Map.of("a",1,"b",2,"c",3));
        check("TreeMap reverse", new ArrayList<>(tm.keySet()).equals(List.of("c","b","a")));

        List<String> byVal = Map.of("x",3,"y",1,"z",2).entrySet().stream()
            .sorted(Map.Entry.comparingByValue()).map(Map.Entry::getKey).toList();
        check("sorted by value", byVal.equals(List.of("y","z","x")));

        Map<String,Integer> sorted = Map.of("x",3,"y",1,"z",2).entrySet().stream()
            .sorted(Map.Entry.comparingByValue())
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a,b)->a, LinkedHashMap::new));
        check("collect into LinkedHashMap keeps sort", new ArrayList<>(sorted.keySet()).equals(List.of("y","z","x")));

        throwsA("null key in TreeMap", NullPointerException.class, () -> new TreeMap<String,Integer>().put(null, 1));
        Map<String,Integer> hm = new HashMap<>(); hm.put(null, 1);
        check("HashMap allows null key", hm.get(null) == 1);

        SequencedMap<String,Integer> sm = new LinkedHashMap<>();
        sm.put("a",1); sm.put("b",2);
        check("reversed view", sm.reversed().firstEntry().getKey().equals("b"));

        Map<String,Integer> big = new HashMap<>();
        for (int i = 0; i < 100; i++) big.put("k" + i, i);
        long a = 0, b = 0;
        for (String k : big.keySet()) a += big.get(k);
        for (var e : big.entrySet()) b += e.getValue();
        check("both loops same sum", a == b && a == 4950);

        System.out.println("passed=" + passed + " failed=" + failed);
        if (failed > 0) System.exit(1);
    }
}

```

```bash
java -cp out com.example.rates.Checks

```

```log
passed=18 failed=0

```

Each check is one thing I claimed above. If you move to JUnit 5 later, each `check(...)` becomes an `@Test` with AssertJ's `assertThat`; I haven't pinned versions for that here because I couldn't resolve them this run.

## What the iterator is doing

`entrySet()`, `keySet()` and `values()` aren't copies. They're views onto the map. That's why `it.remove()` and `setValue` change the map you started with, and why `Map.of("x", 1).entrySet()` fails when you try to set a value:

```log
UOE: java.lang.UnsupportedOperationException: not supported

```

The for-each loop is an `Iterator` underneath. The map counts structural changes (adds and removes of keys). The iterator remembers the count when it started, and every `next()` compares. If they differ, you get `ConcurrentModificationException`. `put` on an *existing* key isn't structural, and the checks confirm that; `put` of a *new* key is. For the concurrent case, `ConcurrentHashMap`'s iterators don't throw this, but they're weakly consistent ([how to use ConcurrentHashMap](https://ggorantala.dev/how-to-use-concurrenthashmap-java/?ref=ggorantala.dev)).

One more consequence: the entry you get in the loop is tied to the map. I held on to the last one from a `HashMap`, put a new value for the key after the loop, and printed it:

```log
entry after put: k=99

```

If you need a snapshot, copy it: `Map.entry(k, v)` (immutable) or `new AbstractMap.SimpleEntry<>(k, v)`. And since `equals`/`hashCode` decide how keys are found, [get them right](https://ggorantala.dev/how-to-implement-equals-hashcode-java/?ref=ggorantala.dev) on custom key types.

## Errors I hit, and the fixes

**`java.util.ConcurrentModificationException` when removing in a for-each.** The map changed structure under the iterator. Use `it.remove()` or `removeIf`, or loop over a copy: `new ArrayList<>(map.keySet())`.

**It *didn't* throw on a one-entry map.** My first check ("put a new key inside the loop throws") failed on a map with one entry. After the single `next()` the iterator already had no next element, so the loop ended before the modification count was ever checked. Output of my first run: `FAIL: put new key in for-each`. Two entries and it throws. The check now has both cases. That's the dangerous kind of bug: green in a quick test, red in production with more data.

**`for-each not applicable to expression type`.** You wrote `for (var e : map)`. A `Map` isn't `Iterable`; loop over `map.entrySet()`.

**`wrong number of type arguments; required 2`.** You wrote `Map.Entry<String>`. It takes both, `Map.Entry<String, Integer>`.

**`NullPointerException` from a `TreeMap`.** Null keys aren't allowed there. A `HashMap` accepts one.

**`UnsupportedOperationException` from `setValue`.** The map came from `Map.of`, `Map.entry` or `Collections.unmodifiableMap`. Copy into a `HashMap` first. See [immutable classes](https://ggorantala.dev/how-to-create-immutable-class-java/?ref=ggorantala.dev) for why those maps are built that way.

## Before this goes near production

- Prefer `entrySet()` over `keySet()` \+ `get()` as a habit.
- If the map is shared between threads, a plain `HashMap` loop can fail in worse ways than an exception. Use `ConcurrentHashMap`, or don't share it.
- Java 21 adds `SequencedMap` on `LinkedHashMap` and `TreeMap`: `reversed()` gives a reversed view (checked, `reversed().firstEntry()` is the last key). On Java 17 or older that call won't compile. `Main` itself compiles with `--release 17`.
- Next to read: [PriorityQueue and TreeMap](https://ggorantala.dev/how-to-use-priorityqueue-treemap-java/?ref=ggorantala.dev) and [unit tests with JUnit 5](https://ggorantala.dev/how-to-write-unit-tests-junit-5-java/?ref=ggorantala.dev); both are on the way.

## The short version

- Read pairs with `entrySet()` or `forEach`. Read values only with `values()`.
- Remove with `removeIf` or the iterator's `remove()`. Never `map.remove` inside a for-each.
- Changing values is fine; adding or removing keys isn't.
- Sort by streaming the entries, and collect into a `LinkedHashMap` if the order has to survive.
- Test with more than one entry.

## Questions people ask about looping over a map

**What is the best way to iterate over a Map in Java?** `for (Map.Entry<K, V> e : map.entrySet())` for general use, `map.forEach((k, v) -> ...)` when you only read. Both are one pass over the entries.

**Can I remove entries from a Map while iterating?** Yes, through the iterator's `remove()` or `entrySet().removeIf(...)`. Calling `map.remove` directly inside a for-each loop throws `ConcurrentModificationException` in most cases.

**How do I iterate a Map in sorted order?** Copy it into a `TreeMap` for key order, or stream `entrySet()` with `Map.Entry.comparingByValue()` for value order. Collect into a `LinkedHashMap` to keep the result.

**Why is my HashMap order different from insertion order?** `HashMap` makes no ordering promise. Use `LinkedHashMap` for insertion order.

## References

- Java SE 21 API: `java.util.Map`, `Map.Entry`, `HashMap`, `TreeMap`, `LinkedHashMap`, `SequencedMap`, `ConcurrentModificationException`. [https://docs.oracle.com/en/java/javase/21/docs/api/](https://docs.oracle.com/en/java/javase/21/docs/api/?ref=ggorantala.dev)
- JEP 431: Sequenced Collections. [https://openjdk.org/jeps/431](https://openjdk.org/jeps/431?ref=ggorantala.dev)