What is the best way to transform and filter a Java Map using streams efficiently?

I have a Java Map, and I want to use Java Map Stream operations to convert values (e.g., from String to Integer) and filter out certain entries in a single pass.

For example, given this input:

Map<String, String> input = new HashMap<>();
input.put("a", "1234");
input.put("b", "2345");
input.put("c", "3456");
input.put("d", "4567");

I want to:

Convert all values to Integers. Remove entries where the value is odd. Collect the result without calling .entrySet().stream() twice.

Currently, my approach is:

Map<String, Integer> output = input.entrySet().stream()
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> Integer.parseInt(e.getValue())
        ))
        .entrySet().stream()
        .filter(e -> e.getValue() % 2 == 0)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

This works and produces {a=1234, c=3456}, but I’m wondering:

How can I perform both the transformation and filtering in a single stream operation, calling .collect() only once at the end?

Instead of first converting values and then filtering in a second stream operation, you can apply both transformations within the same stream:

Map<String, Integer> output = input.entrySet().stream()
        .filter(e -> Integer.parseInt(e.getValue()) % 2 == 0) // Convert & filter at once
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> Integer.parseInt(e.getValue())
        ));

Why this works:

We parse and filter at the same time in .filter(), reducing the need for a second pass. We collect directly at the end, so .entrySet().stream() is only called once.

You can also move filtering into toMap() by using an optional filtering condition inside a stream pipeline:

Map<String, Integer> output = input.entrySet().stream()
        .map(e -> Map.entry(e.getKey(), Integer.parseInt(e.getValue()))) // Convert values
        .filter(e -> e.getValue() % 2 == 0) // Remove odd values
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  • .map() converts the values before filtering, avoiding duplicate operations.
  • .filter() then removes odd numbers before collecting into the final map.

Another approach is using Collectors.filtering(), which allows filtering within the collector itself:

Map<String, Integer> output = input.entrySet().stream()
        .collect(Collectors.toMap(
                Map.Entry::getKey,
                e -> Integer.parseInt(e.getValue()),
                (v1, v2) -> v1, // Merge function (not needed here)
                () -> new HashMap<>()
        ));
output.entrySet().removeIf(e -> e.getValue() % 2 != 0); // Remove odd values after collection

Why this works:

  • We first convert everything and collect into a HashMap.
  • Then we remove odd values after collecting, which can be useful in cases where filtering before collecting isn’t feasible.