Java Optional Is Not So Obvious

Semyon Kirekov
Level Up Coding
Published in
7 min readDec 6, 2020

--

NullPointerException is such an annoying thing in the Java world, and Optional was brought to solve the problem. We can’t say that it has absolutely gone, but we made huge steps. Many popular libraries and frameworks put Optional within their ecosystem. For instance, Spring Data repositories return Optional<Entity>, instead of null.

I think that some programmers have become so excited about Optional that they started to overuse it and apply it with the wrong patterns. In this article I have listed some misuses of this monad and offered my way to handle the given problems.

Optional must never be null

I guess that no additional explanations are required here. Assigning Optional to null breaks the whole idea of the class. No client of your API will ever check the Optional for null equality. Instead of null you should prefer to use Optional.empty().

Know the API

Since new Java versions have been released, Optional was enhanced with new features that allow to achieve less verbose code.

Optional.isPresent

The idea is simple. If the name is empty, return the default one. We can rewrite it in a more pretty way.

Optional.orElse

Let’s make something more complicated. Suppose, we need to return an Optional of the person’s name. If the name contains in the set of allowed names, the value should be present, otherwise not. Here is the verbose approach.

Optional of the name

Using Optional.filter can make this code look much better.

Optional of the name — a better approach

This advice applies not only to Optional but to the whole process of development.

If the language has its own solution, try to use it before coming up with your own.

Prefer value-based alternatives to generic ones

There are special non-generic Optional classes. OptionalInt, OptionalLong, and OptionalDouble. If you need to work with primitive types, it is better to use value-based containers. In this case, there would be no additional boxing and unboxing procedures that might affect performance.

Don’t disregard the laziness

Optional.orElse is a convenient approach for returning default values. But if the computation of the default value is rather expensive, this can cause some performance issues.

Optional with lacked performance

Even if the table is present in the cache, it will be fetched from the remote server each time. Thankfully, this can be easily solved with Optional.orElseGet.

Optional with no performance issues

Optional.orElseGet accepts lambda that shall be calculated only if it applied on an empty container.

Don’t make Optional from collections

Although I’ve seen that no so often, it happens sometimes.

Optional of list

Any collections is a container itself which emptiness can be determined without additional classes.

Empty list — a better approach

Unnecessary Optionals makes the API too hard to work with.

Don’t pass Optional as a parameter

Here we start discussing the most arguable parts of the article.

Why shouldn’t we pass Optional as a parameter? At first glance that seems logical. It helps us to avoid the unexpected NullPointerException, right? Well, perhaps it is. But the problem has deeper roots.

Optional parameters

Firstly, the API has unnecessary bounds. On each invocation a user has to wrap values with Optional. Even known constant values.

Secondly, applySettings has 4 potential different behaviours that are based on Optionals presence. That breaks the SRP (single responsibility principle).

Finally, we have no idea how does the method interprets an Optional. Does it just replace the absent value with the default one or does it completely change the business logic? Or maybe it just throws NoSuchElementException.

If we look at the javadoc of Optional, we can find an interesting note.

Optional is primarily intended for use as a method return type where there is a clear need to represent “no result,” and where using null is likely to cause errors.

Optional literally defines possible no result object. Passing possible no result in the method sounds like a bad idea. That means that the API knows too much about the context and makes decisions that it shouldn’t be aware of.

So, how can we improve this code? If age and role must always be present, we can just eliminate the Optional from the parameters and handle its absence on the top level.

Optional parameters eliminated

Now the calling code is in control of the argument values. That becomes even more crucial if you develop a framework or a library.

On the contrary, if age or role might be omitted this approach is not going to work. In that case, the best way would be to declare separate methods for different user’s needs.

Separated ‘applySettings’ method

Perhaps that looks a little verbose, but now a user has the ability to do exactly what they need avoiding unexpected errors.

Don’t use Optional as a class field

I heard many opinions on this point. Some people think that storing Optionals in classes directly helps to reduce NullPointerException tremendously. My friend who works in a well-known startup says that this approach is considered as a pattern in their company. Others think that this breaks the whole concept of the monad.

Although storing Optional directly may sound like a good idea, I think that this can bring more problems than benefits.

No serializability

Optional does not implement Serializable interface. That’s not a bug, this was done manually because Optional was designed to use as a return type only. So, the class that has any Optional field cannot be serialized.

I think that this point is the least convincing one. Because in the modern world of distributed systems and microservices platform based serialization has no such great significance as it used to be.

Keeping unnecessary references

Optional is the object that a user needs only for a few milliseconds. After that, it can be deleted by the garbage collector. But if we keep Optional as a class field, it can be stored there till the program stops. You won’t probably notice any performance issues on a small project. Anyway, if we are talking about a large application with dozens of beans that may lead to different consequences.

Poor integration with Spring Data/Hibernate

Suppose we are building a simple Spring Boot application. We need to retrieve values from the table. This can be easily done by declaring the entity and the corresponding repository.

A simple entity with a repository

That’s the possible result of personRepository.findAll().

Person(id=1, firstName=John, lastName=Brown)
Person(id=2, firstName=Helen, lastName=Green)
Person(id=3, firstName=Michael, lastName=Blue)

Suppose that firstName and lastName fields are nullable. We don’t want to mess up with NullPointerException. So, let’s replace simple field declarations with Optional.

An entity with Optional fields

Now it’s all broken.

org.hibernate.MappingException: Could not determine type for: java.util.Optional, at table: person, for columns: [org.hibernate.mapping.Column(firstname)]

Hibernate just cannot map the value from the database to Optional.

But some things do work appropriately

I have to admit, that everything is not so bad after all. Some frameworks integrated with Optional correctly.

Jackson

Let’s declare simple endpoint and DTO.

PersonDTO
Simple endpoint

Here is the result for GET /person/1.

{
"id": 1,
"firstName": "John",
"lastName": "Brown"
}

As you can see, we have no configuration. Everything works out of the box. Let’s try to replace String with Optional<String>

PersonDTO with Optionals

In order to test multiple cases, I replaced one assignment with Optional.empty().

Endpoint with Optionals

Surprisingly everything still works correctly.

{
"id": 1,
"firstName": "John",
"lastName": null
}

So, we can use Optional as a field value safely with Spring Web, right? Well, kind of. There are some corner cases.

SpringDoc

SpringDoc is a library for Spring Boot applications that generates Open Api Schema automatically.

Here is what we’ve got for the GET /person/{id} endpoint.

"PersonDTO": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
}
}
}

Looks pretty decisively. But we need to make id property mandatory. This can be done by either using @NotNull or @Schema(required = true). Let’s add some more interesting details. What if we put @NotNull on an Optional field?

PersonDTO with mandatory id

That shall lead to interesting results.

"PersonDTO": {
"required": [
"firstName",
"id"
],
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
}
}
}

As we can see, id is the required field indeed. But firstName is too. And here starts the tricky part. Optional cannot be the required one because its whole sense means that the value might be omitted. Anyway, we mispointed the framework with only one additional annotation.

What’s the problem? For example, if the frontend part uses any types generator based on Open Api Schema, that would corrupt the data format and might lead to unpleasant consequences.

Solution

What can we do about it? The answer is simple. Use Optional only for getters.

PersonDTO with Optional getters

Now this class can be safely used either as DTO or a Hibernate entity. Optional has no affect on the data. It just wraps nullable values to handle absent data appropriately.

But there is one disadvantage. This approach cannot be fully integrated with Lombok. Optional getters are not supported by the library. And it won’t probably happen. At least we can consider that by some discussions on Github.

I wrote an article about Lombok and I think that is a brilliant tool. And the fact that is not integrated with the Optional-Getter pattern is not pleasant.

For now the only work-around is to define getters manually.

Conclusion

That’s all I wanted to say about java.util.Optional. I know that it’s an arguable topic. If you have any questions or suggestion, please leave your comments. Thanks for reading!

--

--

Java Dev and Team Lead. Passionate about clean code, tea, pastila, and smooth jazz/blues. semyon@kirekov.com