javalogo

Tackling Map.get()’s null-returning anti-pattern

In the beginning was Dictionary and HashTable, and they returned null when a call to get() was made. And the darkness prevailed over the land. And programmers found many NullPointerExceptions in production and confusion reigned on the face of the deep.

Then lo Map and HashMap were created. And there was great rejoicing, because their performance was greatly improved. And yea the royal order of code scribes did thus feel a great advance.

But confusion still reigned on the face of the deep, and null was returned on every call to get() . . .

Backward-compatibility: Java’s main strength?

Java’s main strength is arguably its slavish devotion to backwards-compatibility. There is no doubt this has allowed large enterprises to have the stability they need in their codebases. But it also has downsides. It results in a strange landscape of oddly-named classes in the standard library. Names like Vector and HashTable will be immediately familiar to C/C++ developers yet they are broken or unusable in key situations and have therefore been “replaced” by others curiously named ArrayList and HashMap. I put “replaced” in quotes because Vector and HashTable are still there, all these years later, it’s just that when you talk to a lot of Java developers, they say, “What’s a vector?”

Even though these are some of the earliest examples, that kind of thing is happening all the time in Java. The world evolves but Java still retains naming, behavior, or other strange characteristics that are leftover relics.

Java Map Behavior

Speaking of Java maps, did you notice how outdated they are today, in a world that recognizes null as a billion-dollar mistake and an abomination of unspeakable horrors?

Here is the API documentation for Map.get():


get(Object key)
Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.


Why it’s bad

Other libraries, and even languages, in the JVM ecosystem have now moved away from this behavior, starting in as early as 2008. Scala is one example. In Scala, Map.get() returns an Option, which is similar to Java’s Optional.

If you somehow doubt that returning null is a bad thing, let me be the first to appreciate your doubt. Why should you believe anything I say? I’m only an average developer with ten years experience on large, scalable web applications and services. I don’t claim to be a guru, but I can definitely pass along the message of the gurus. For example, turn with me to page 110 of Clean Code, where we see a big bold header reading “Don’t Return Null.” Uncle Bob writes:

uncle_bob_scolding

I think that any discussion about error handling should include mention of the things we do that invite errors. The first on the list: returning null. I can’t begin to count the number of applications I’ve seen in which nearly every other line was a check for null . . . When we return null, we are essentially creating work for ourselves and foisting problems upon our callers. All it takes is one missing null check to send an application spinning out of control.

What to do about it

In our modern Java world, we can do better than making use of the standard Map.get() function, which returns null and violates basic decency. Here are some options.

Use getOrDefault

In Java 8, the Map interface got some updates, and we now have a better option than get: getOrDefault(). The API documentation looks like this:


getOrDefault(Object key, V defaultValue)
Returns the value to which the specified key is mapped, or defaultValue if this map contains no mapping for the key.


In simple cases where a default value is easy to create, this will suffice. For example, if you have mapped some type of key to a String value, you can call getOrDefault(), pass an empty string as the default, and clean up a lot of null checks out of the code.

Unfortunately, there are many cases where it will be difficult or messy to create a default value. One case that I ran into just yesterday was while using a bit of glue code in an application that kept track of all the DropWizard Metrics Counter objects in a map. What I really wanted to do was get the value of the counter. First, I had to look up the counter in the map. Second, I had to make sure that I got a real counter back. Third, and finally, I had to call getValue() on that counter. If the Counter didn’t exist in the map, then I wanted to use zero as the default for its value.

There’s no easy and clean way to make a default Counter object that always returns 0 when getValue() is called. It actually made the code more messy to do things that way. So what did I do? Using Optional is one possibility that I landed on.

Use Optional

It may be better to use Optional in the more complex case where an object with behavior (rather than a value) is what you are looking up in the map. By using Optional.ofNullable() you can quickly convert the null returned from get() into an empty Optional. This is much cleaner and easier to deal with and it’s a one-liner. It looks something like this.

Optional<Counter> counterOpt = Optional.ofNullable(counters.get("myCounter"));
long currentCount = counterOpt.map(Counter::getValue).orElse(0L);

Optional Maps on Steroids

This can be even cleaner with about five minutes of effort. If you end up doing this a lot in your application, you can hack Java so you can have an Optional-returning map. Just write a wrapper around HashMap (or whichever flavor of the Map interface you happen to be using) that converts the result of get() to an Optional.

Here’s what part of a wrapper class around HashMap might look like.

public class HashMapReturningOptional<K, V> {

    private final HashMap<K, V> map;

    public HashMapReturningOptional() {
        this.map = new HashMap<>();
    }

    public Optional<V> get(K key) {
        return Optional.ofNullable(map.get(key));
    }

    // other code elided

}

The name HashMapReturningOptional is chosen only for the purposes of clearly communicating this specific example. Choose something better in your specific case.

If I used a HashMapReturningOptional, my previous example of getting a counter value would be reduced down to just the single line:

long currentCount = counters.get("myCounter").map(Counter::getValue).orElse(0L);

Or just write your own encapsulation

There’s some sage old wisdom that’s been passed around the Java community for years that says you shouldn’t expose specific data structure usage to client code. Instead, by the principles of encapsulation, you should create an abstraction around it and communicate a particular semantic to your callers. In my case, maybe storing DropWizard Metrics Counters in a map isn’t as good as creating a CountersRegistry class or something similarly named. It could have a method like getCounterValue(String counterName) and encapsulate my use of Optional within, giving me the flexibility to later change how I am handling the null case without cascading the change throughout the application.

That code might look like this:

public long getCounterValue(String counterName) {
  Optional<Counter> counterOpt = Optional.ofNullable(counters.get(counterName));
  return counterOpt.map(Counter::getValue).orElse(0L);
}

And, of course, the benefit of this is that now all code throughout the application that needs the counter value only needs to call CounterRegistry.getCounterValue().

Small Warning

I’ll leave you with a small warning. Beware that you consider the downstream effects of defaulting to a value in the null case. Null is definitely worse than a default value, but a default value can also lead to unexpected behavior. Be sure that the JavaDoc on methods returning a default value clearly communicates the fact that the method will return a default value if it can’t find the actual value. That contract needs to be clear!

Leave a Reply

Your email address will not be published. Required fields are marked *