Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Given a Map<K, V> and a List<K>, start with the key list and look up each key. The direct result is a List<V>:

List<V> values = keys.stream()
        .map(map::get)
        .toList();

This follows the order of keys and keeps repeated keys. It also puts null in the result for a key with no mapping. The details that matter most are what to do about missing keys and whether the result should be mutable. If you need List<T> rather than List<V>, add a conversion from each value to T.

Why the lookup starts with the key list

The operation is: for each key in keys, look up its value in map and append that value. Starting the stream from keys makes the requested list—not the map’s iteration order—determine the result order.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Map<Integer, String> names = Map.of(
        1, "Ada",
        2, "Grace"
);

List<Integer> ids = List.of(2, 1);

List<String> result = ids.stream()
        .map(names::get)
        .toList();

// ["Grace", "Ada"]

Repeated keys are repeated lookups, so they produce repeated values. For example, keys ["b", "a", "b"] produce the value for "b", then "a", then "b" again. Use .distinct() before the lookup only if you deliberately want to keep the first occurrence of each key and discard later ones.

By contrast, map.values() or a stream over map.entrySet() returns values in the map’s encounter order and ignores the supplied key list. A HashMap does not promise a stable encounter order; LinkedHashMap and TreeMap have defined ordering behavior of their own. For the order of the caller’s keys, always iterate over keys. See the Java Map API.

Choose what happens when a key is missing

Map.get(key) returns null when the map has no mapping for that key. If the map permits null values, it can also return null when the key is present and explicitly maps to null. Choose the behavior that fits your data rather than assuming every null means the same thing.

Keep a position for every requested key

List<V> values = keys.stream()
        .map(map::get)
        .toList();

This preserves the list’s length and positions, but a missing key appears as null. A stored null value is indistinguishable from a missing key in this result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Skip keys that are absent

List<V> values = keys.stream()
        .filter(map::containsKey)
        .map(map::get)
        .toList();

This keeps values only for keys that have a mapping and preserves the relative order of those keys. If a present key maps to null, that null is kept. The distinction between get() and containsKey() is documented in the Java Map API.

Substitute a fallback

V fallback = ...;

List<V> values = keys.stream()
        .map(key -> map.getOrDefault(key, fallback))
        .toList();

getOrDefault() uses the fallback if the key has no mapping. A key explicitly mapped to null still has a mapping, so its result can remain null. If you want to replace both absent keys and null values, use a null check instead:

List<V> values = keys.stream()
        .map(key -> {
            V value = map.get(key);
            return value != null ? value : fallback;
        })
        .toList();

Use containsKey() when null is a valid stored value and must be treated differently from absence.

Fail when a key is absent

A loop makes validation and error reporting straightforward:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
List<V> values = new ArrayList<>(keys.size());

for (K key : keys) {
    if (!map.containsKey(key)) {
        throw new IllegalArgumentException("Missing key: " + key);
    }
    values.add(map.get(key));
}

This accepts a present key whose value is null. If that is not valid for your application, check for null separately and report that condition explicitly.

Choose the result’s mutability and Java version

Stream.toList() is available from Java 16 and returns an unmodifiable list. It preserves the stream’s encounter order, but does not promise a particular implementation type. Attempts to modify it, such as calling add(), throw UnsupportedOperationException. See the Java Stream.toList() documentation.

For Java 8 through 15, a common option is:

List<V> values = keys.stream()
        .map(map::get)
        .collect(Collectors.toList());

Do not rely on Collectors.toList() to return a particular list type or to guarantee mutability. If you need a mutable ArrayList, request one explicitly:

List<V> values = keys.stream()
        .map(map::get)
        .collect(Collectors.toCollection(ArrayList::new));

Both collector forms are available from Java 8. The collector contracts are described in the Java Collectors API.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If you want an unmodifiable list on Java 10 or later, Collectors.toUnmodifiableList() is another option. Unlike Stream.toList(), it rejects null elements. List.copyOf() also creates an unmodifiable list and rejects null elements, so it is not suitable when a missing lookup is being represented as null. See the Java List.copyOf() documentation.

When the result really needs to be List<T>

A lookup in a Map<K, V> produces a V. To produce a T, apply a conversion after the lookup. This example omits absent keys:

List<T> result = keys.stream()
        .filter(map::containsKey)
        .map(map::get)
        .map(converter)
        .toList();

For example, if userMap maps IDs to User objects and UserDto.from converts a user to a DTO:

List<UserDto> users = userIds.stream()
        .filter(userMap::containsKey)
        .map(userMap::get)
        .map(UserDto::from)
        .toList();

The filter is a policy choice: remove it if missing keys should reach the converter as null or be handled another way. A reusable helper can accept the conversion function:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
static <K, V, T> List<T> selectAndMap(
        Map<K, V> map,
        List<K> keys,
        Function<? super V, ? extends T> converter) {

    return keys.stream()
            .filter(map::containsKey)
            .map(map::get)
            .map(converter)
            .toList();
}

The wildcard bounds allow a converter that accepts a supertype of V and returns a subtype of T. Add imports for java.util.Map, java.util.List, and java.util.function.Function. For Java 8–15, replace .toList() with .collect(Collectors.toList()).

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When a loop is the better fit

Streams are concise for a simple lookup, but a loop is often clearer when you need a mutable result, detailed validation, logging, multiple conditions, or a custom failure policy:

List<V> values = new ArrayList<>(keys.size());

for (K key : keys) {
    if (!map.containsKey(key)) {
        continue; // Or substitute a value or throw an exception.
    }
    values.add(map.get(key));
}

This version skips absent keys but retains a null value for a present key if the map permits one. If every requested key should have a result, omit the continue policy and decide explicitly whether absence should mean null, a fallback, or an exception.

Performance and concurrent changes

The operation performs one lookup per requested key. With a typical hash-based map, lookup is expected to be constant time, so looking up n keys is generally expected to take about O(n). A tree-based map typically takes O(log m) per lookup for a map of size m, giving about O(n log m). These are implementation-dependent expectations, not guarantees of the Map interface. The output list itself uses space proportional to the number of values retained.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If another thread can change the map while lookups run, the result may reflect different map states across the list. In particular, filtering with containsKey() and then calling get() does not make those two calls an atomic lookup. If you need a consistent snapshot or atomic behavior, establish it using the synchronization or data-structure guarantees appropriate to your application.

Quick choice guide

Requirement Use
Follow key-list order Stream or loop over keys
Keep duplicate requests Do not call distinct()
Represent missing keys as null .map(map::get)
Skip absent keys .filter(map::containsKey).map(map::get)
Use a fallback for absent keys .map(k -> map.getOrDefault(k, fallback))
Require a mutable ArrayList Collectors.toCollection(ArrayList::new)
Convert values from V to T Add a second .map(converter)
Need detailed validation Use a conventional loop

If keys is null, calling keys.stream() throws NullPointerException. Decide at the API boundary whether null should be rejected or explicitly treated as an empty list; do not silently choose the latter if it would hide a caller error. A null map likewise normally indicates invalid input and should not be silently ignored.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.