containsKey()

Check whether a key is present, tell a missing key from a null value, and avoid the double lookup and race of check-then-get.

Signature
boolean containsKey(Object key)
  • Added in Java Java 1.2

Parameters

ParameterTypeDescription
$keyObjectThe key whose presence is tested.

Return value

booleantrue if the map contains a mapping for the key, even when that mapping's value is null.

Errors

ClassCastException if the key's type is not compatible with the map (optional); NullPointerException if the key is null and the map does not permit null keys, such as Map.of or ConcurrentHashMap.

containsKey answers whether the map has a mapping for a key. It returns true even when the value stored is null, which makes it the only way to tell "absent" from "present with null" on a map that allows null values.

When the difference matters

A JSON PATCH body parsed into a Map<String, Object> is the classic case. "name": null means "clear the name", while no name key means "leave it alone". get returns null for both; containsKey separates them.

Check-then-get is usually one call too many

if (stock.containsKey(sku)) {          // lookup 1
    int qty = stock.get(sku);          // lookup 2
}
Integer qty = stock.get(sku);          // one lookup
if (qty != null) { ... }

When the map never stores null values, a single get plus a null check does the same job. On a shared ConcurrentHashMap the two-step version is also a race: another thread can remove the key between the check and the read. The same goes for check-then-put; use putIfAbsent, computeIfAbsent or merge, which do the check and the write together.

How keys are matched

HashMap uses hashCode and equals and runs in constant time on average. TreeMap uses compareTo or its comparator and runs in logarithmic time. That lets you build a case-insensitive map with new TreeMap<>(String.CASE_INSENSITIVE_ORDER), which suits HTTP header names, since they are case-insensitive. Copying such a map into a HashMap loses that behaviour.

containsValue looks the other way, and it has to scan every entry, so it is linear in the size of the map. If you need it often, keep a second map from value to key.

Nulls

containsKey(null) returns false on a HashMap without a null key, but throws NullPointerException on Map.of, ConcurrentHashMap and a TreeMap with natural ordering. Validate request input before using it as a key.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Telling a field explicitly set to null in a PATCH request from a field the client left out.
  • Checking for a header in a case-insensitive header map before applying CORS or caching rules.
  • Validating that a parsed configuration map has every required key before the service starts.

Frequently asked questions

Should I use containsKey or get != null?

If the map never stores null values, get plus a null check is one lookup instead of two. Use containsKey when null values are possible and you need to know whether the key is present.

Is containsKey O(1)?

On HashMap and LinkedHashMap it is constant time on average. On TreeMap it is O(log n). containsValue is O(n) on all of them because it scans the entries.

How do I check if a key exists ignoring case?

Store the entries in a TreeMap created with String.CASE_INSENSITIVE_ORDER, or normalise keys with toLowerCase(Locale.ROOT) when you put them and when you look them up.

More examples

Absent versus null in a PATCH body

get returns null for both name and bio, but only containsKey shows that the client asked to clear name and did not mention bio.

Example · java
List<String> missing(Map<String, Object> body, List<String> required) {
    List<String> out = new ArrayList<>();
    for (String field : required) {
        if (!body.containsKey(field)) {
            out.add(field);
        }
    }
    return out;
}

void main() {
    Map<String, Object> body = new LinkedHashMap<>();
    body.put("email", "[email protected]");
    body.put("name", null); // the client sent "name": null to clear it

    IO.println("missing: " + missing(body, List.of("email", "name", "password")));
    IO.println("name sent: " + body.containsKey("name") + ", value: " + body.get("name"));
    IO.println("bio sent: " + body.containsKey("bio") + ", value: " + body.get("bio"));
}
// Output:
// missing: [password]
// name sent: true, value: null
// bio sent: false, value: null

Case-insensitive header names

The TreeMap compares keys with its comparator, so case is ignored. Copying into a HashMap goes back to equals, which is case-sensitive.

Example · java
void main() {
    Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    headers.put("Content-Type", "application/json");
    headers.put("X-Request-Id", "req-7");

    IO.println(headers.containsKey("content-type"));
    IO.println(headers.get("x-request-id"));

    Map<String, String> plain = new HashMap<>(headers);
    IO.println(plain.containsKey("content-type"));
}
// Output:
// true
// req-7
// false

One lookup instead of two

Because this map never stores null values, get plus a null check answers the same question with half the lookups. containsValue works, but it scans every entry.

Example · java
void main() {
    Map<String, Integer> stock = new HashMap<>(Map.of("SKU-1", 5, "SKU-2", 5));

    if (stock.containsKey("SKU-1")) {
        IO.println("contains then get: " + stock.get("SKU-1"));
    }

    Integer qty = stock.get("SKU-3");
    IO.println(qty == null ? "SKU-3 not stocked" : "qty " + qty);

    IO.println("any SKU with 5: " + stock.containsValue(5));
}
// Output:
// contains then get: 5
// SKU-3 not stocked
// any SKU with 5: true

Discussion

  • Be the first to comment on this lesson.