DEV Community

Cover image for Dissecting the Hibernate LazyInitializationException: Cause and Solutions
Jonas Bockstal
Jonas Bockstal

Posted on

Dissecting the Hibernate LazyInitializationException: Cause and Solutions

Any Java developer who has worked with the Spring and Hibernate frameworks has undoubtedly already ran into the LazyInitializationException. It is a common error that occurs when accessing lazily loaded entities outside of a session. This exception can be frustrating and time-consuming to resolve, especially when working on large-scale projects. The purpose of this article is to provide a comprehensive guide for addressing this exception. I will explain what it is and the reason why it occurs. I will also present six possible solutions, each with their own inherent drawbacks. Throughout the article, I will refer to a GitHub repository that I have set up with the intention of providing concrete examples. By the end, you will have a better understanding of how to handle the LazyInitializationException and be able to resolve the issue quickly and efficiently.

Understanding the LazyInitializationException

The LazyInitializationException is an exception that can occur when working with Hibernate. The application throws this error when we try to access lazily loaded entities outside of a session. When an entity is lazily loaded, its associated data is not retrieved from the database until it is explicitly requested. This approach can help improve performance by reducing the amount of data that needs to be retrieved from the database. However, it also forces us to think about how we access this lazily loaded data throughout our code.

To understand what it means to access data outside of a session, we first need to understand what a session is and what its life cycle looks like. Hibernate's Session interface is the main tool that is used to communicate with Hibernate. It provides an API that enables us to create, read, update, and delete persistent objects. It has a very simple life cycle that is bounded by the beginning and ending of a logical transaction. Once a new transaction begins, the session is opened. It then performs some operations, and is closed again when the transaction ends. When we operate on objects during the session, they get attached to that session. The changes are detected and saved upon closing. After closing, Hibernate breaks the connections between the objects and the session. If we access lazily loaded entities after this point, we will run into a LazyInitializationException. This makes sense, because once the connection between the object and the session is broken, we can't tell Hibernate to fetch the lazily loaded data from the database anymore.

It is essential to understand this in order to be able to fix the exception properly. Once we see how the error is triggered, we can also understand that a solution to this problem can come in two forms: we either initialize all the properties we need afterwards before the session is closed, or we make sure to access the lazily loaded entities when the session is still open. The next sections of this article will go through setting up the code in such a way that it triggers the exception, after which I will provide practical solutions for fixing it. My hibernate-lazyinitializationexception GitHub repository includes a main branch that is in a faulty state, meaning that it is in a state that throws the exception when the only controller method is invoked. Every other branch contains one of the solutions that I will propose further on.

Setting Up the Error

Before we get into the solutions, I want to give an overview of how I have set up my code in such a way that it triggers the exception.

To start off, I have created a Hotel entity that has a lazy loaded collection of Rooms.

@Entity
public class Hotel {
    @OneToMany(fetch = FetchType.LAZY, 
        cascade = CascadeType.PERSIST)
    private Set rooms;

    // Other code
}
Enter fullscreen mode Exit fullscreen mode

In the HotelController, I have defined an method that, when invoked, will result in the exception being thrown. The HotelService simply calls the HotelRepository, which extends the JpaRepository interface and returns a Hotel object based on its id. The key to solving the error is understanding the life cycle of the logical transaction and the Hibernate session. When the findById method of the repository is executed, a new transaction is started and the session is opened. Once an entity is found and returned, both the transaction and session are closed. When we try loop through the rooms in the controller - so after the session is already closed - the LazyInitializationException is thrown.

@RestController
public class HotelController {
    @Autowired 
    private HotelService hotelService;

    @GetMapping("/hotels/{id}")
    public Hotel getHotel(@PathVariable Long id) {
        Hotel hotel = this.hotelService.findHotelById(id);

        // The next line will trigger a 
        // LazyInitializationException
        hotel.getRooms().forEach(
            room -> { System.out.println(room.getCode()); });

        return hotel;
    }
}

@Service
public class HotelService {
    @Autowired 
    private HotelRepository hotelRepository;

    public Hotel findHotelById(Long id) {
        return hotelRepository.findById(id).orElseThrow();
    }
}

@Component  
public interface HotelRepository extends JpaRepository {}
Enter fullscreen mode Exit fullscreen mode

Possible Solutions

The LazyInitializationException can be a tricky error to fix, but there are several practical solutions available that can help us address it. In this section, I will discuss some of the most common solutions for fixing the error. As I have stated in the previous section, a solution can come in two forms. This is because the error occurs when trying to access lazily loaded entities outside of a session. We either initialize all the necessary properties before the session is closed, or we make sure to access the lazily loaded entities when the session is still open.

Initializing the Necessary Properties

I will start off by discussing four solutions that revolve around initializing all the necessary properties before the session is closed. Note that I have annotated the service method with the @Transactional annotation in some cases. This annotation ensures that a new transaction is started when the method it is placed on starts executing. Consequently, a session will be open when we access the lazily loaded entities, which means we will not trigger an error.

DTO Projection

The first solution for fixing the LazyInitializationException is implemented on the dto-projection branch of the demo project. In DTO projection, we create a new class that represents a projection of an entity, containing only the fields we need. This means that, instead of returning a Hotel object to the controller, we instantiate and initialize a HotelDTO object while the session is still open. This HotelDTO object will have a collection of rooms that is initialized with the lazily loaded rooms from the Hotel object. If we then access those rooms inside the controller, we will no longer run into an exception.

A possible downside of DTO projection is that it can increase the complexity of the application by adding additional classes and mappings. This can make the application harder to maintain and more difficult to understand. Additionally, DTO projection can also increase the memory usage of the application, as it requires creating additional objects to represent the projections. Despite these downsides, DTO projection can be an effective solution when used properly.

@Service
public class HotelService {
    @Autowired private HotelRepository hotelRepository;

    @Transactional
    public HotelDTO findHotelById(Long id) {
        Hotel hotel =
            hotelRepository.findById(id).orElseThrow();
        Set rooms = new HashSet<>(hotel.getRooms());
        return new HotelDTO(hotel.getName(), rooms);
    }
}

public class HotelDTO {
    private String name;
    private Set rooms;

    // Other code
}
Enter fullscreen mode Exit fullscreen mode

Hibernate.initialize()

Another solution for fixing the LazyInitializationException can be found on the hibernate-initialize-method branch of the demo project. It consists of using the Hibernate.initialize() method to initialize the lazily loaded objects explicitly. This method forces Hibernate to load the associated data immediately, allowing us to access it outside of the session. Concretely, this means that we call this method when the session is still open, after which we can access the lazily loaded entities when the session is closed. While this solves our problem, using Hibernate.initialize() can lead to performance issues because it requires additional queries to initialize the associated entities.

@Service
public class HotelService {
    @Autowired 
    private HotelRepository hotelRepository;

    @Transactional
    public Hotel findHotelById(Long id) {
        Hotel hotel =
            hotelRepository.findById(id)
            .orElseThrow();
        Hibernate.initialize(hotel.getRooms());

        return hotel;
    }
}
Enter fullscreen mode Exit fullscreen mode

EntityGraph

Since JPA 2.1, entity graphs can also provide a solution for solving a LazyInitializationException. I have implemented an example of this solution on the entity-graph branch of the demo project. In essence, entity graphs allow us to switch between lazy and eager loading at runtime. They are used to define a graph of entities that should be loaded when an entity is fetched from the database. This allows us to specify which entities should be eagerly loaded and which should be lazily loaded. We can then choose - at runtime - whether to use the default fetching strategies, or to apply the strategy that is expressed via the entity graph. By using entity graphs, we can avoid the LazyInitializationException by loading all the required entities when we fetch the entity from the database.

Entity graphs are a powerful solution for managing relationships between entities and avoiding the LazyInitializationException, but there are also some downsides to using them. Defining and managing entity graphs can be complex, especially if our application has a large number of entities and complex relationships. It may take some time to understand how to define the entity graph and ensure that it loads all the required entities. Additionally, If we eagerly load too many entities using entity graphs, it can lead to performance issues such as slow query times and increased memory usage. Finally, they are only supported in JPA 2.1 or higher.

@Entity
@NamedEntityGraph(name = "graph.hotel.rooms",
    attributeNodes = @NamedAttributeNode("rooms"))
public class Hotel {
    @OneToMany(fetch = FetchType.LAZY, cascade =
        CascadeType.PERSIST)
    private Set rooms;

    // Other code
}

@Service
public class HotelService {
    @Autowired 
    private EntityManager entityManager;

    public Hotel findHotelByIdUsingEntityGraph(Long id) {
        EntityGraph entityGraph =
            this.entityManager
            .getEntityGraph("graph.hotel.rooms");

        Map hints = new HashMap<>();
        hints.put("javax.persistence.fetchgraph", 
            entityGraph);

        return this.entityManager.find(Hotel.class, id, 
            hints);
    }
}
Enter fullscreen mode Exit fullscreen mode

Eager Loading

We can also solve the LazyInitializationException by using eager loading. However, by applying this solution, we will also lose all the benefits of lazy loading. An example of this implementation can be found on the eager-loading branch of the demo project. Eager loading involves loading all the required data from the database at once, rather than on an as-needed basis. By doing so, we can ensure that all the required data is loaded before the session is closed. However, it is essential to be careful when using eager loading, as it can have a negative impact on performance if used excessively. This is due to the fact that it can result in the unnecessary loading of large amounts of data.

@Entity
public class Hotel {
    @OneToMany(fetch = FetchType.EAGER, 
        cascade = CascadeType.PERSIST)
    private Set rooms;

    // Other code
}
Enter fullscreen mode Exit fullscreen mode

Accessing Entities When the Session Is Still Open

In the remainder of this section, I will discuss two solutions that focus on accessing the lazily loaded entities when the session is still open.

Open Session in View Pattern

The first solution of this kind is to use the Open Session in View pattern. To demonstrate this, I have altered the application.properties file on the open-session-in-view-pattern branch of the demo project. This pattern applies the session-per-request pattern, which involves keeping the Hibernate session open until the view has been fully rendered. This will solve the LazyInitializationException, because the session will still be open when the code in the controller is executed.

The Open Session in View pattern is often considered to be an anti-pattern, which is a typical solution to a recurring problem that is ineffective or runs the risk of doing more harm than good. Even so, the pattern is active in Spring Boot web applications by default. However, as of Spring Boot 2.0, developers get a warning of the possible downsides of using the pattern. It can cause problems such as performance issues, because the Hibernate session is kept open until the view has been fully rendered, which can lead to database connection pooling issues.

spring.jpa.open-in-view = true
Enter fullscreen mode Exit fullscreen mode

Session Management

Finally, proper manual session management can help us avoid the LazyInitializationException errors as well. By keeping the session open for the required period and using proper session management techniques, we can ensure that the required data is loaded from the database and accessed before the session is closed. However, proper session management can be challenging to implement correctly and can add additional complexity to our application. I have implemented an example of this on the session-management branch of the demo project.

@RestController
public class HotelController {
    @Autowired 
    private SessionFactory sessionFactory;

    @GetMapping("/hotels/{id}")
    public Hotel getHotel(@PathVariable Long id) {
        Session session = sessionFactory.openSession();
        Transaction tx = null;
        Hotel hotel = null;

        try {
            tx = session.beginTransaction();
            hotel = session.get(Hotel.class, id);

            // The next line will no longer trigger a
            // LazyInitializationException
            hotel.getRooms().forEach(
                room -> {System.out.println(room.getCode())});

            tx.commit();
        } catch (HibernateException e) {
            if (tx != null)
                tx.rollback();
            e.printStackTrace();
        } finally {
            session.close();
        }

        return hotel;
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

The LazyInitializationException can be a frustrating error to deal with. However, by understanding what causes the error and using the practical solutions discussed in this article, it can be effectively addressed and fixed. I have proposed six different possible solutions, each with its own possible drawbacks. These solutions are:

  • DTO projection
  • The Hibernate.initialize() method
  • Entity graphs
  • Eager loading
  • The Open Session in View pattern
  • Proper manual session management

By choosing a solution wisely, taking into account all its inherent possible shortcomings, we can ensure that our application runs smoothly and avoids the LazyInitializationException error.

References

Fränkel, F. (2021, March 28). A (definitive?) guide on LazyInitializationException. (2021, March 28). A Java Geek. https://blog.frankel.ch/guide-lazyinitializationexception/
Hibernate (Hibernate JavaDocs). (n.d.). https://docs.jboss.org/hibernate/orm/6.2/javadocs/org/hibernate/Hibernate.html#initialize(java.lang.Object)
Janssen, T. (2021, October 31). JPA Entity Graphs: How to Define and Use a @NamedEntityGraph. Thorben Janssen. https://thorben-janssen.com/jpa-21-entity-graph-part-1-named-entity/
Janssen, T. (2021, October 31). LazyInitializationException - What it is and the best way to fix it. Thorben Janssen. https://thorben-janssen.com/lazyinitializationexception/#Use_a_NamedEntityGraph_to_initialize_an_association
LazyInitializationException (Hibernate JavaDocs). (n.d.). https://docs.jboss.org/hibernate/orm/6.2/javadocs/org/hibernate/LazyInitializationException.html
Session (Hibernate JavaDocs). (n.d.). https://docs.jboss.org/hibernate/orm/6.2/javadocs/org/hibernate/Session.html
Mihalcea, V. (2020, March 17). The best way to handle the LazyInitializationException. Vlad Mihalcea. https://vladmihalcea.com/the-best-way-to-handle-the-lazyinitializationexception/

This post first appeared on my LinkedIn profile.

Top comments (0)