|

Common Hibernate Exceptions Every Developer Must Know


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.


Every framework has a set of exceptions that indicate common error scenarios. Understanding them will help you turn a task that could drag along for days into a quickly-implemented feature. When working with Hibernate, they tell you about issues with your database queries, mapping mistakes, and problems during write operations.

In this article, I want to show you the most common exceptions and explain how you can avoid or fix them.

Query-Related Exceptions

These are the most common Hibernate exceptions. Sometimes, they are caused by a simple typo in your statement or because you called the wrong Query interface method. However, they can also indicate potential performance issues or data inconsistencies.

NonUniqueResultException

All JPA implementations throw the NonUniqueResultException if you call the getSingleResult method on a Query interface and the executed query returns more than one record.

javax.persistence.NonUniqueResultException: query did not return a unique result: 2
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:128)
	at org.hibernate.query.internal.AbstractProducedQuery.getSingleResult(AbstractProducedQuery.java:1588)
	...

The NonUniqueResultException most often occurs because:

  • You intentionally wrote a query that returns multiple records and tried executing it by calling the getSingleResult() instead of the getResultList() method. To be honest, that happens to me quite regularly. Luckily, it’s very quick and easy to fix by calling the getResultList() method.
  • You wanted to create a query that returns only one record, but the WHERE clause isn’t as restrictive as you thought. In that case, please check my Guide to JPQL. It tells you everything you need to know to improve your query.
  • You wrote a query that was supposed to return only one record and you stumbled upon inconsistent data. If that happens, then it’s hopefully just some artificially created test data. But, in any case, you should double-check your business logic and add a unique constraint to your table model.

QuerySyntaxException

If Hibernate throws a QuerySyntaxException, you have made a mistake in your JPQL query. In most cases, the mistake will be a simple typo in one of JPQL’s reserved keywords.

Book book = entityManager
					.createQuery("SELECT b FROM Book b LFET JOIN b.author a WHERE a.id=:id", Book.class)
					.setParameter("id", id)
					.getSingleResult();

As you can see in the following log message, Hibernate tells you which part of the query it didn’t understand. In this example, I misspelled the word LEFT in my LEFT JOIN clause.

java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: 
unexpected token: LFET near line 1, column 49 
[SELECT b FROM com.thorben.janssen.entity.Book b LFET JOIN b.author a WHERE a.id=:id]

SQLGrammarException

An SqlGrammarException is very similar to a QuerySyntaxException. It indicates a mistake in a native SQL statement.

Book book = (Book) entityManager
					.createNativeQuery("SELECT b FROM Book b LFET JOIN Author a on b.author_id = a.id WHERE a.id=:id", Book.class)
					.setParameter("id", id)
					.getSingleResult();

Unfortunately, Hibernate’s exception doesn’t provide a lot of information about the actual error. It only tells you that it was unable to extract the ResultSet. But, if you take a look at the end of the stack trace, you should find the exception thrown by your JDBC driver. It usually tells you why the query failed.

javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
	... 
Caused by: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
	... 
Caused by: org.postgresql.util.PSQLException: ERROR: syntax error at or near "LFET"
  Position: 22

LazyInitializationException

The LazyInitializationException might not look like a query-related exception because it happens much later. Hibernate throws it if you try to initialize a lazily fetched association without an active Hibernate Session. This usually happens in your UI layer after your backend committed the transaction and closed the Session. Even though it might not look like it, it’s caused by a query that didn’t fetch the required association.

As you can see in the message below, Hibernate tells you which association it wasn’t able to initialize.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: 
com.thorben.janssen.entity.Author.books, could not initialize proxy - no Session

You can fix this exception in multiple ways. The easiest and most commonly used solution is a JOIN FETCH clause. You can use it in your JPQL query to tell Hibernate to initialize the referenced association.

		List<Author> authors = entityManager.createQuery("SELECT a FROM Author a JOIN FETCH a.books", Author.class).getResultList();

MultipleBagFetchException

If you model your to-many associations as java.util.Lists and try to initialize a number of them using a JOIN FETCH clause, Hibernate will throw a MultipleBagFetchException.

List<Book> books = entityManager.createQuery("""
	SELECT b 
	FROM Book b 
		JOIN FETCH b.authors a 
		JOIN FETCH b.reviews r""", 
	Book.class).getResultList();

As I explained in more detail in a previous article, you can fix this exception in two ways:

1. You can change the attribute type to a java.util.Set.

@Entity
public class Book {
	
	@ManyToMany
	private Set<Author> authors = new HashSet<Author>();
	
	@OneToMany(mappedBy = "book")
	private Set<Review> reviews = new HashSet<Review>();
	
	...
}

2. You can use multiple queries that use only one JOIN FETCH clause each. This approach works because Hibernate ensures that a Session contains only one entity object for each database record. If you select the same one with different fetching behavior, Hibernate will merge them in memory.

TypedQuery<Book> q = em.createQuery("""
		SELECT DISTINCT b
		FROM Book b 
			JOIN FETCH b.authors a
		WHERE b.id = 1""",
		Book.class);
q.setHint(QueryHints.PASS_DISTINCT_THROUGH, false);
List<Book> books = q.getResultList();

q = em.createQuery("""
		SELECT DISTINCT b
		FROM Book b 
			JOIN FETCH b.reviews r
		WHERE b.id = 1""",
		Book.class);
q.setHint(QueryHints.PASS_DISTINCT_THROUGH, false);
books = q.getResultList();

Mapping-Related Exceptions

I will keep this section short because mapping-related exceptions are rare. That’s mostly because mapping is defined once and used during the entire project.

MappingException: The Increment Size of the [author_seqence] Sequence Is Set to [50] in the Entity Mapping While the Associated Database Sequence Increment Size Is [1]

If you use a database sequence to generate your primary key values and define a @SequenceGenerator, you might get the following exception:

MappingException: The increment size of the [author_seqence] sequence 
is set to [50] in the entity mapping while the associated database sequence 
increment size is [1].

Hibernate throws this exception because it provides an optimization based on the default allocationSize of the @SequenceGenerator. Based on that default setting, the database sequence should get incremented by 50 every time you request a new value. Hibernate then uses the value from the sequence and increments it 49 times internally before requesting another value. This drastically reduces the number of executed JDBC statements.

During the deployment of your persistence layer, Hibernate checks if the referenced database sequence gets incremented by the defined allocationSize. If that’s not the case, it throws a MappingException.

I recommend fixing this exception by increasing the step size on your database sequence. It enables you to use Hibernate’s optimization to improve the performance of your insert operations. If you cannot change the database sequence, you need to set the allocationSize of the @SequenceGenerator to one.

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "author_seq")
@SequenceGenerator(name = "author_seq", allocationSize = 1)
private Long id;

Update-Related Exceptions

Hibernate throws this kind of exception to inform you about problems during write operations. The two most common ones are the OptimisticLockException and the PersistentObjectException. While the first one is expected to occur at runtime, the second one is always an implementation error.

OptimisticLockException

JPA and Hibernate provide two build-in mechanisms to manage concurrent updates. One of them is called optimistic locking. It causes an OptimisticLockException whenever two or more transactions try to update the same database record at the same time. That lets all except the first transaction fail and prevents the concurrent modification of the record.

javax.persistence.RollbackException: 
Error while committing the transaction
	...
Caused by: javax.persistence.OptimisticLockException: 
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.thorben.janssen.entity.Book#4]
	...
Caused by: org.hibernate.StaleObjectStateException: 
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.thorben.janssen.entity.Book#4]
	...

Optimistic and pessimistic locking are easy and complex at the same time, but that’s a whole other topic. If you want to learn more about it, please join my Hibernate Performance Tuning Online Training.

Technically, there are a few options for fixing an OptimisticLockException. But realistically, all of them require you to merge the operations performed in the parallel transactions. The best and most reliable way to do that is to show an error message to the user when the exception occurs. The user can then find the differences between both changes, resolve errors or inconsistencies, and perform another update.

PersistentObjectException: Detached Entity Passed to Persist

There are two main scenarios in which Hibernate throws this exception.

The first happens when you configure a primary key generation strategy for an entity and set the primary key value programmatically before persisting a new entity. You can easily fix that by removing the code that sets the primary key value. If you’re using field-based access, you should also remove the setter method for the primary key attribute. This prevents you from accidentally setting that attribute in the future.

The second scenario happens when you call the persist method of the EntityManager with a detached entity.

javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.thorben.janssen.entity.Author

This is usually the case because you got an entity object from a client application and tried to persist it. In this case, you can fix the exception by calling the merge method on the EntityManager.

Conclusion

If you’re using a framework to implement your application, you should be familiar with its most common exceptions. Hibernate is a complex and powerful framework. So, it’s no surprise that you need to be familiar with a bunch of exceptions.

Most of them are query-related because querying data is highly flexible and the most used part of the framework. The good news is that all of these exceptions include a good description of the error so that you can easily fix it.

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.