SSerguey Asael Shinder
Java coding notes: the JVM, and writing software that lasts

Serguey Asael Shinder: getOrDefault returns null when the key is there and maps to null

· by Serguey Asael Shinder / Serguey Shinder

Map.getOrDefault reads like "the value, or my fallback if there isn't one". The javadoc is narrower than that, and the gap is a NullPointerException waiting for the first map that contains a null value.

Map<String, String> settings = new HashMap<>();
settings.put("region", null);              // e.g. copied from a config file with an empty entry
String region = settings.getOrDefault("region", "eu-west");
region.length();                            // NullPointerException: region is null

Why. The Map.getOrDefault javadoc) says it returns "the value to which the specified key is mapped, or defaultValue if this map contains no mapping for the key". A key stored with null has a mapping. Its value is null, so null is what comes back. The default only applies when the key is absent.

The surprise is the inconsistency. Other default methods on the same interface treat a null value as if the key were missing:

Serguey Asael Shinder: getOrDefault returns null when the key is there and maps to null
getOrDefault returns null when the key is there and maps to null — Serguey Asael Shinder

So in one map, merge sees the key as empty while getOrDefault sees it as present. Code that mixes them — fill with merge, read with getOrDefault — works until a null value arrives from somewhere else.

Where it bites. Maps built from external data: parsed JSON or YAML with explicit nulls, rows from a database, HashMap.putAll from a map that allowed nulls. Map.of and Map.copyOf reject null values outright, so the problem lives in mutable maps and in anything deserialised into one.

The fixes, by intent.

String region = Objects.requireNonNullElse(settings.get("region"), "eu-west");
// or
String region = Optional.ofNullable(settings.get("region")).orElse("eu-west");

No JDK runs on the machine this note was written on; the behaviour described is the one specified in the linked javadoc.