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.
boolean containsKey(Object key)- Added in Java Java 1.2
Parameters
| Parameter | Type | Description |
|---|---|---|
$key | Object | The 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
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.
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: nullCase-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.
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
// falseOne 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.
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