Benchmarking Optional in Java

Oscar Ablinger
5 min readMar 14, 2021

--

I’ve been advised to avoid Optional in code out of performance concerns — often from people with a lot more experience and Java knowledge than me. So I wondered how much impact it really has and decided to dig a bit deeper.

What hides behind Optional?

Before doing some benchmarks, let’s look at what Optional even consists of and why it will be slower than using null values. So if we look at the Optional class and strip away all methods, we end up with the following:

public final class Optional<T> {
private final T value;
}

So as expected it is really just a wrapper around an nullable object. This means that creating the class just requires allocating 16 or 20 bytes (12 bytes for the header and 4 or 8 bytes for the object reference depending on whether you run on a 32-bit or 64-bit system).

You’ll also note that the class itself is final. This allows Java to skip dynamic type checks when invoking methods.

The second point of performance concern is the usage of lambdas, which Optional does encourage. Many people wrongly assume that that always means that Java has to create an instance of an anonymous class for that. And before dynamic invocation was added in Java 7, this was the case.

At least since Java 1.8 these lambdas are instead compiled to static methods — even if they have captured variables.

How much slower is Optional?

After looking a bit closer at the class, I wrote some benchmarks. For this I used the well-known library JMH and decided to test both, the Optional class as well as the OptionalInt class.

First, let’s have a look at the Optional class: I created two methods that do the same thing, but one returns null while the other returns an Optional.

public String isEvenNullable(int n) {
if (n % 2 == 0) {
return "even";
} else {
return null;
}
}
public Optional<String> isEvenOptional(int n) {
if (n % 2 == 0) {
return Optional.of("even");
} else {
return Optional.empty();
}
}

Then I tested each of them in three scenarios:

  • Just calling the method and returning what it returns
  • Calling the method and either transforming the returned string to upper case or, if none is returned, return a static string.
  • Like the second scenario, but in the success-case I now also append a captured variable

The exact code for this can be found in the Github repository for these benchmarks.

The result of these scenarios are:

The dark bar is the code with the value 1 (ending up in a null/empty value), while the light one uses 2 (ending in a success value).

In the null/empty case, the times are almost identical. This makes sense as you do not need to create an object even for the Optional-based implementation.

Based on the first two tests in the graph, the creation of the Optional objects takes around 3 nanoseconds on my machine. This means that in the next two tests, the method reference provided to the Optional method “map” takes an extra two nanoseconds in comparison to calling it directly on the String object.

The only notable increase in runtime can be found, when a capturing lambda is used. This increased the average runtime by 12 nanoseconds.

So the difference is?

…not a lot. If you plan to create multiple Optional objects or create a few thousands per second, you will notice a difference, but if you don’t, you’ll likely better off optimizing other parts of your code.

So I wouldn’t necessarily use it in a high-performance algorithm that you’re implementing or in a closed system that is small enough to have an overview over what can and cannot be null, but for an API or when it would naturally happen (e.g. after calling “findFirst” in a stream) I’ll probably continue to use it.

What about OptionalInt?

With Optional, you have to wrap a pointer additionally, but what about value types? If I want them to be nullable, I need to box them anyways, so how much slower is using OptionalInt versus the standard Integer?

I wanted to use the same three scenarios as above and… I can’t. Apparently OptionalInt does not offer a “map” method nor any other method that would return another OptionalInt. If I want to map something, I’d have to transform it into a stream and that would not be a direct comparison to Integer anymore.

So, I only tried the first two scenarios, but for the second one created two methods for the nullable style: one that uses the Integer as the boxed value and one that unboxes it. This results in the following values:

OptionalInt is faster than Integer

This result was really unexpected, so much so that I re-run the benchmarks and increased the amount of warmups and iterations, but the result was the same: Most of the times OptionalInt is faster than using an Integer.

A closer look at OptionalInt and Integer

OptionalInt has two fields: one for the int and one for a boolean signifying whether or not it has a value or not.

public final class OptionalInt {
private final boolean isPresent;
private final int value;
}

Okay, makes a lot of sense. And Integer also looks like one would expect:

public final class Integer extends Number
implements Comparable<Integer>, Constable, ConstantDesc {
private final int value;
}

So Integer has one field less (since no value is represented by a null instead of a special Integer instance), but somehow requires 2 nanoseconds longer to be created. The culprit of this is probably the function that creates it: valueOf

@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

So Integer sacrifices a bit of performance in order to decrease the required memory by using a cache.

This explains why it takes a bit longer to create one, but why is working with it faster? Well, with Integers, you always need to check whether or not the variable is null. And this null-check presumably takes longer than just reading a boolean.

Note that the speed advantage will likely fade as you approach the limits of the RAM of the host machine.

Conclusion

Optional is slightly slower than the null-based approach, but not a lot. I’d still prefer the explicitness and security that it offers over returning values null.

Of course, I’m not suggesting that it is used everywhere, since it does have an overhead, but when you design APIs then it’s usually more important that the user of it can interact with it more safely than shaving a few nanoseconds off of its runtime — and you should never have to return more than one Optional anyways.

Sign up to discover human stories that deepen your understanding of the world.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Oscar Ablinger
Oscar Ablinger

Written by Oscar Ablinger

Hi, I’m a Software Engineer that just writes on here about whatever he finds out in a given week. Could be scripts, insights or explanations of things I learned

No responses yet

Write a response