|

LazyInitializationException – What it is and the best way to fix it


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.


The LazyInitializationException is one of the most common exceptions when working with Hibernate. There are a few easy ways to fix it. But unfortunately, you can also find lots of bad advice online. The proclaimed fixes often replace the exception with a hidden problem that will cause trouble in production. Some of them introduce performance issues, and others might create inconsistent results.

In the following paragraphs, I will explain to you what the LazyInitializationException is, which advice you should ignore, and how to fix the exception instead.

When does Hibernate throw a LazyInitializationException

Hibernate throws the LazyInitializationException when it needs to initialize a lazily fetched association to another entity without an active session context. That’s usually the case if you try to use an uninitialized association in your client application or web layer.

Here you can see a test case with a simplified example.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

TypedQuery<Author> q = em.createQuery(
		"SELECT a FROM Author a",
		Author.class);
List<Author> authors = q.getResultList();
em.getTransaction().commit();
em.close();

for (Author author : authors) {
	List<Book> books = author.getBooks();
	log.info("... the next line will throw LazyInitializationException ...");
	books.size();
}

The database query returns an Author entity with a lazily fetched association to the books this author has written. Hibernate initializes the books attributes with its own List implementation, which handles the lazy loading. When you try to access an element in that List or call a method that operates on its elements, Hibernate’s List implementation recognizes that no active session is available and throws a LazyInitializationException.

How to NOT fix the LazyInitializationException

As I wrote at the beginning, you can find lots of bad advice on how to fix the LazyInitializationException. Let me quickly explain which suggestions you should ignore.

Don’t use FetchType.EAGER

Some developers suggest changing the FetchType of the association to EAGER. This, of course, fixes the LazyInitializationException, but it introduces performance problems that will show up in production.

When you set the FetchType to EAGER, Hibernate will always fetch the association, even if you don’t use it in your use case. That obviously causes an overhead that slows down your application. But it gets even worse if you don’t use the EntityManager.find method and don’t reference the association in a JOIN FETCH clause. Hibernate then executes an additional query to fetch the association. This often results in the n+1 select issue, which is the most common cause of performance issues.

So please, don’t use FetchType.EAGER. As explained in various articles on this blog, you should always prefer FetchType.LAZY.

Avoid the Open Session in View anti-pattern

When using the Open Session in View anti-patter, you open and close the EntityManager or Hibernate Session in your view layer. You then call the service layer, which opens and commits a database transaction. Because the Session is still open after the service layer returned the entity, the view layer can then initialize the lazily fetched association.

But after the service layer committed the database transaction, there is no active transaction. Because of that, Hibernate executes each SQL statement triggered by the view layer in auto-commit mode. This increases the load on the database server because it has to handle an extra transaction for each SQL statement. At the end of each of these transactions, the database has to write the transaction log to the disc, which is an expensive operation.

The increased pressure on your database isn’t the only downside of this anti-pattern. It can also produce inconsistent results because you are now using 2 or more independent transactions. As a result, the lazily fetched association might return different data than your service layer used to perform the business logic. Your view layer then presents both information together and it might seem like your application manages inconsistent data.

Unfortunately, Spring Boot uses the Open Session in View anti-pattern by default. It only logs a warning message.

2020-03-06 16:18:21.292  WARN 11552 --- [  restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning

You can deactivate it by setting the spring.jpa.open-in-view parameter in your application.properties file to false.

Don’t use hibernate.enable_lazy_load_no_trans

Another suggestion you should avoid is to set the hibernate.enable_lazy_load_no_trans configuration parameter in the persistence.xml file to true. This parameter tells Hibernate to open a temporary Session when no active Session is available to initialize the lazily fetched association. This increases the number of used database connections, database transactions and the overall load on your database.

OK, so what should you do instead?

How to fix the LazyInitializationException

The right way to fix a LazyInitializationException is to fetch all required associations within your service layer. The best option for that is to load the entity with all required associations in one query. Or you can use a DTO projection, which doesn’t support lazy loading and needs to be fully initialized before you return it to the client.

Let’s take a closer look at the different options to initialize lazily fetched association and at the best way to use DTO projections.

Initializing associations with a LEFT JOIN FETCH clause

The easiest way to load an entity with all required associations is to perform a JPQL or Criteria Query with one or more LEFT JOIN FETCH clauses. That tells Hibernate to not only fetch the entity referenced in the projection but also to fetch all associated entities referenced in the LEFT JOIN FETCH clause.

Here you can see a simple example of such a query.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

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

em.getTransaction().commit();
em.close();

for (Author a : authors) {
	log.info(a.getName() + " wrote the books " 
		+ a.getBooks().stream().map(b -> b.getTitle()).collect(Collectors.joining(", "))
	);
}

The query selects Author entities, and the LEFT JOIN FETCH clause tells Hibernate to also fetch the associated Book entities. As you can see in the generated SQL statement, Hibernate not only joins the 2 corresponding tables in the FROM clause, it also added all columns mapped by the Book entity to the SELECT clause.

select
	author0_.id as id1_0_0_,
	books1_.id as id1_2_1_,
	author0_.name as name2_0_0_,
	author0_.version as version3_0_0_,
	books1_.author_id as author_i7_2_1_,
	books1_.authorEager_id as authorEa8_2_1_,
	books1_.publisher as publishe2_2_1_,
	books1_.publishingDate as publishi3_2_1_,
	books1_.sells as sells4_2_1_,
	books1_.title as title5_2_1_,
	books1_.version as version6_2_1_,
	books1_.author_id as author_i7_2_0__,
	books1_.id as id1_2_0__ 
from
	Author author0_ 
left outer join
	Book books1_ 
		on author0_.id=books1_.author_id

As you can see in the log messages, the query returned an Author entity with an initialized books association.

16:56:23,169 INFO  [org.thoughtsonjava.lazyintitializationexception.TestLazyInitializationException] - Thorben Janssen wrote the books Hibernate Tips - More than 70 solutions to common Hibernate problems

Use a @NamedEntityGraph to initialize an association

You can do the same using a @NamedEntityGraph. The main difference is that the definition of the graph is independent of the query. That enables you to use the same query with different graphs or to use the same graph with various queries.

I explained @NamedEntityGraphs in great detail in a previous article. So, I keep the explanation short. You can define the graph by annotating one of your entity classes with a @NamedEntityGraph annotation. Within this annotation, you can provide multiple @NamedAttributeNode annotations to specify the attributes that Hibernate shall fetch.

@NamedEntityGraph(
    name = "graph.authorBooks",
    attributeNodes = @NamedAttributeNode("books")
)
@Entity
public class Author { ... }

To use this graph, you first need to get a reference to it from your EntityManager. In the next step, you can set it as a hint on your query.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

EntityGraph<?> entityGraph = em.createEntityGraph("graph.authorBooks");
TypedQuery<Author> q = em.createQuery("SELECT a FROM Author a", Author.class)
		.setHint("javax.persistence.fetchgraph", entityGraph);
List<Author> authors = q.getResultList();

em.getTransaction().commit();
em.close();

for (Author a : authors) {
	log.info(a.getName() + " wrote the books " 
		+ a.getBooks().stream().map(b -> b.getTitle()).collect(Collectors.joining(", "))
	);
}

If you look at the generated SQL statement, you can see that there is no difference between a LEFT JOIN FETCH clause and a @NamedEntityGraph. Both approaches result in a query that selects all columns mapped by the Author and the Book entity and return Author entities with an initialized books association.

select
	author0_.id as id1_0_0_,
	books1_.id as id1_2_1_,
	author0_.name as name2_0_0_,
	author0_.version as version3_0_0_,
	books1_.author_id as author_i7_2_1_,
	books1_.authorEager_id as authorEa8_2_1_,
	books1_.publisher as publishe2_2_1_,
	books1_.publishingDate as publishi3_2_1_,
	books1_.sells as sells4_2_1_,
	books1_.title as title5_2_1_,
	books1_.version as version6_2_1_,
	books1_.author_id as author_i7_2_0__,
	books1_.id as id1_2_0__ 
from
	Author author0_ 
left outer join
	Book books1_ 
		on author0_.id=books1_.author_id

EntityGraph to initialize an association

The EntityGraph API provides you the same functionality as the @NamedEntityGraph annotation. The only difference is that you use a Java API instead of annotations to define the graph. That enables you to adjust the graph definition dynamically.

As you can see in the code snippet, the API-based definition of the graph follows the same concepts as the annotation-based definition. You first create the graph by calling the createEntityGraph method. In the next step, you can add multiple attributes nodes and subgraphs to the graph. I explain all of that in great detail in JPA Entity Graphs: How to Dynamically Define and Use an EntityGraph.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

EntityGraph<Author> entityGraph = em.createEntityGraph(Author.class);
entityGraph.addAttributeNodes("books");
TypedQuery<Author> q = em.createQuery("SELECT a FROM Author a", Author.class)
		.setHint("javax.persistence.fetchgraph", entityGraph);
List<Author> authors = q.getResultList();

em.getTransaction().commit();
em.close();

for (Author a : authors) {
	log.info(a.getName() + " wrote the books " 
		+ a.getBooks().stream().map(b -> b.getTitle()).collect(Collectors.joining(", "))
	);
}

After you defined the graph, you can use it in the same way as a @NamedEntityGraph, and Hibernate generates an identical query for both of them.

select
	author0_.id as id1_0_0_,
	books1_.id as id1_2_1_,
	author0_.name as name2_0_0_,
	author0_.version as version3_0_0_,
	books1_.author_id as author_i7_2_1_,
	books1_.authorEager_id as authorEa8_2_1_,
	books1_.publisher as publishe2_2_1_,
	books1_.publishingDate as publishi3_2_1_,
	books1_.sells as sells4_2_1_,
	books1_.title as title5_2_1_,
	books1_.version as version6_2_1_,
	books1_.author_id as author_i7_2_0__,
	books1_.id as id1_2_0__ 
from
	Author author0_ 
left outer join
	Book books1_ 
		on author0_.id=books1_.author_id

Using a DTO projection

Fetching all required associations when you load the entity fixes the LazyInitializationException. But there is an alternative that’s an even better fit for all read operations. As I showed in a previous article, DTO projections provide significantly better performance if you don’t want to change the retrieved information.

In these situations, you can use a constructor expression to tell Hibernate to instantiate a DTO object for each record in the result set.

EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

TypedQuery<AuthorDto> q = em.createQuery(
		"SELECT new org.thoughtsonjava.lazyintitializationexception.dto.AuthorDto(a.name,b.title) FROM Author a JOIN a.books b",
		AuthorDto.class);
List<AuthorDto> authors = q.getResultList();

em.getTransaction().commit();
em.close();

for (AuthorDto author : authors) {
	log.info(author.getName() + " wrote the book " + author.getBookTitle());
}

Hibernate then generates an SQL statement that only selects the columns that are mapped by the attributes that you reference in the constructor call. This often reduces the number of selected columns and improves the performance even further.

select
	author0_.name as col_0_0_,
	books1_.title as col_1_0_ 
from
	Author author0_ 
inner join
	Book books1_ 
		on author0_.id=books1_.author_id

Conclusion

If you have used Hibernate for a while, you probably had to fix at least one LazyInitializationException. It’s one of the most common ones when working with Hibernate.

As I explained in this article, you can find lots of advice online on how to fix this exception. But a lot of these suggestions only replace the exception with problems that will show up in production.

There are only 2 good solutions to this problem:

  1. You initialize all required associations when you load the entity using a LEFT JOIN FETCH clause or a @NamedEntityGraph or the EntityGraph API.
  2. You use a DTO projection instead of entities. DTOs don’t support lazy loading, and you need to fetch all required information within your service layer.