Filter a Map by Keys and Values using Java Stream

Learn to filter a Map by keys or values or both, using the Java 8 Stream filter() and collect() methods. These easy-to-follow code snippets aim to write generic functions that we can use to filter any Map by its keys or values.

For demo purposes, we are using the following Map:

Map<Integer, User> usersMap = Map.of(
        1, new User(1, "Alex"),
        2, new User(2, "Allen"),
        3, new User(3, "Brian"),
        4, new User(4, "Bob"),
        5, new User(5, "Charles"),
        6, new User(6, "David"),
        7, new User(7, "Don"),
        8, new User(8, "Dave"));

The User is a simple record type with two components: id and name.

record User(Integer id, String name) {
}

1. Filter a Map by Keys

The general syntax for filtering a Map by keys and collecting entries into a new Map is given below. All we need to do is to provide the condition used for filtering.

map.entrySet()
  .stream()
  .filter(key -> {condition})
  .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

In the following example, we have created a generic function that we will use to filter entries from the demo map. Note that to make the function generic, we have facilitated passing the condition as Predicate.

public static final class Filters {

  private Filters() {
    throw new AssertionError("Cannot be instantiated");
  }

  public static <K, V> Map<K, V> byKey(Map<K, V> map, Predicate<K> predicate) {

    Objects.requireNonNull(map);
    Objects.requireNonNull(predicate);

    return map.entrySet()
        .stream()
        .filter(item -> predicate.test(item.getKey()))
        .collect(Collectors.toMap(
            Map.Entry::getKey, Map.Entry::getValue));
  }

  //...
}

Now, suppose we want to filter and collect all the map entries where the key value is greater than 4 (key > 4). Then we can use the Filters.byKey() function as follows:

Predicate<Integer> predicate = key -> key > 4;
Map<Integer, User> filteredMap = Filters.byKey(usersMap, predicate);
System.out.println(filteredMap);

The program output:

{5=User[id=5, name=Charles], 6=User[id=6, name=David], 7=User[id=7, name=Don], 8=User[id=8, name=Dave]}

2. Filter a Map by Values

Next, we have modified the previous method to support filtering based on entry values. This time, inside the Stream.filter(), we are applying the condition on entry value.

public static final class Filters {

  //...

  public static <K, V> Map<K, V> byValue(Map<K, V> map, Predicate<V> predicate) {

    Objects.requireNonNull(map);
    Objects.requireNonNull(predicate);

    return map.entrySet()
      .stream()
      .filter(item -> predicate.test(item.getValue()))
      .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
  }
}

Let us test this method by filtering the map for all values which start with “D”.

Predicate<User> valuePredicate = user -> user.name().startsWith("D");
Map<Integer, User> filteredMapByValue = Filters.byValue(usersMap, valuePredicate);
System.out.println(filteredMapByValue);

The program output:

{6=User[id=6, name=David], 7=User[id=7, name=Don], 8=User[id=8, name=Dave]}

3. Filter a Map by Keys and Values, Both

Sometimes, we may need to use complex conditions such that filtering needs to be applied in both, keys and values. In such cases, we have two options:

  • Write the complex condition in the filter() method, explicitly.
  • Chain the Filters.byKey() and Filters.byValue() methods.

Suppose, we want to filter the Map by keys and values, such that, the key is greater than 4 and the value starts with “D”. We can chain the generic method as follows:

Predicate<Integer> predicate = key -> key > 4;
Predicate<User> valuePredicate = user -> user.name().startsWith("D");

Map<Integer, User> filteredMapByKeyAndValue = Filters.byValue(
    Filters.byKey(usersMap, predicate), valuePredicate);
System.out.println(filteredMapByKeyAndValue);

The program output:

{8=User[id=8, name=Dave], 6=User[id=6, name=David], 7=User[id=7, name=Don]}

Similarly, we can write the explicit condition in the filter method as follows:

Map<Integer, User> filteredMapByKeyAndValue_V2 = usersMap.entrySet().stream()
  .filter((entry) -> entry.getKey() > 4 && entry.getValue().name().startsWith("D"))  // both conditions
  .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
System.out.println(filteredMapByKeyAndValue);

The program output:

{6=User[id=6, name=David], 7=User[id=7, name=Don], 8=User[id=8, name=Dave]}

4. Conclusion

This short tutorial discussed how to filter a Map by keys, values or both using the Java 8 stream API. These generic functions can help you to extract the common functionality in utility methods thus making the code more clean and maintainable.

Happy Learning !!

Source Code on Github

Comments

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.