How to use Java 8’s Optional with Hibernate
Take your skills to the next level!
The Persistence Hub is the place to be for every Java developer. It gives you access to all my premium video courses, monthly Java Persistence News, monthly coding problems, and regular expert sessions.
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>.
@Entity public class Book { ... @Column 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.”); }
Summary
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.
JSR-335 don’t recommend using optional attributes, because optional class isn’t serializable.
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.
Regards,
Thorben
Very Nice information
Thanks!
I’m glad you like it.
Excellent tip. thanks for info.