Featured Image with Sidebar

How to use Java 8’s Optional with Hibernate

By Thorben Janssen

Java 8 introduced Optional<T> as a container object which may contain null values. It’s often used to indicate to a caller that a value might be null and that it need to be handled to avoid NullPointerExceptions.

Sounds pretty useful, right?

So why not use them in your persistence layer for optional entity attributes or when loading entities that may or may not exist?

Until the release of Hibernate 5.2, the reason was pretty simple: It wasn’t supported. And you still have to wait for JPA 2.2 if you don’t want to rely on proprietary features. But that’s a different topic.

After they added support for the Java 8 DateTime API in Hibernate 5.0, the Hibernate team starts to use Java 8 Streams and Optional in their query APIs in Hibernate 5.2. In today’s post, I want to show you how you can use Optional<T> to indicate optional attributes and query results which might not return a result.

Optional attributes

Using Optional<T> for optional entity attributes is probably the most obvious use case. But there is still no direct support for it in Hibernate 5.2. It requires a small trick which also works with older Hibernate versions.

Let’s say; you’re storing books in a database. Some of them are already published, and others are still in progress. In this case, you have a Book entity with an optional publishingDate attribute that might be null.

With previous Java versions, the getPublishingDate() method would just return null. The caller would need to know about the possible null value and handle it. With Java 8, you can return an Optional to make the caller aware of possible null values and to avoid NullPointerExceptions.

But if you just change the type of the publishingDate attribute from LocalDate to Optional<LocalDate>, Hibernate isn’t able to determine the type of the attribute and throws a MappingException.

javax.persistence.PersistenceException: [PersistenceUnit: my-persistence-unit] Unable to build Hibernate SessionFactory
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.persistenceException(EntityManagerFactoryBuilderImpl.java:951)
Caused by: org.hibernate.MappingException: Could not determine type for: java.util.Optional, at table: Book, for columns: [org.hibernate.mapping.Column(publishingDate)]
at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:454)
at org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:421)
at org.hibernate.mapping.Property.isValid(Property.java:226)
at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:595)
at org.hibernate.mapping.RootClass.validate(RootClass.java:265)
at org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:329)
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:489)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:878)
... 28 more

To avoid this Exception, you have to use field-type access and keep LocalDate as the type of the publishingDate attribute. Hibernate is then able to determine the data type of the attribute but doesn’t return an Optional.

And here is the trick: When you use field-type access, you can implement the getter and setter methods in your own way. You can, for example, implement a getPublishingDate() method which wraps the publishingDate attribute in an Optional<LocalDate>.

public class Book {
	private LocalDate publishingDate;
	public Optional getPublishingDate() {
		return Optional.ofNullable(publishingDate);
	public void setPublishingDate(LocalDate publishingDate) {
		this.publishingDate = publishingDate;

Load optional entities

Hibernate 5.2 also introduced the loadOptional(Serializable id) method to the IdentifierLoadAccess interface which returns an Optional<T>. You should use this method to indicate that the result might be empty when you can’t be sure that the database contains a record with the provided id.

The loadOptional(Serializable id) method is similar to the load(Serializable id) method which you already know from older Hibernate versions. It returns the loaded entity or a null value if no entity with the given id was found. The new loadOptional(Serializable id) method wraps the entity in an Optional<T> and therefore indicates the possibility of a null value.

As you can see in the following code snippet, you can use it in the same way as the existing load(Serializable id) method.

Session session = em.unwrap(Session.class);
Optional<Book> book = session.byId(Book.class).loadOptional(1L);

if (book.isPresent()) {
  log.info(“Found book with id [“+book.get().getId()+”] and title [“+book.get().getTitle()+”].”);
} else {
  log.info(“Book doesn’t exist.”);


Hibernate 5 supports the DateTime API classes as data types and extended the existing APIs to use new Java 8 features like Streams and Optional. Most of these changes are just small but they allow you to use the existing APIs with new concepts introduced in Java 8. I hope that we will get the same features with JPA 2.2.

Until that’s the case, we have to rely on the Hibernate specific loadOptional(Serializable id) method to fetch optional entities from the database and the described trick to use Optional for optional entity attributes.

About the author

Thorben is an independent consultant, international speaker, and trainer specialized in solving Java persistence problems with JPA and Hibernate.
He is also the author of Amazon’s bestselling book Hibernate Tips - More than 70 solutions to common Hibernate problems.

Books and Courses

Coaching and Consulting


Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.

    1. Hi Gabriel,

      That’s correct and the reason why you can’t persist an attribute of type Optional.
      But that doesn’t affect the implementation of your getter method. That’s basically the same as if you would use an Optional in your business code.


{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}