Introduction

Hibernate uses lazy loading by default for entity associations. The associated data is not fetched until the getter is called. If the entity becomes detached from the Hibernate session (e.g., returned from a service method, serialized to JSON), accessing the lazy association throws LazyInitializationException because there is no open session to execute the fetch query.

Symptoms

  • org.hibernate.LazyInitializationException: could not initialize proxy [com.example.User#1] - no Session
  • failed to lazily initialize a collection of role: com.example.Order.items, could not initialize proxy - no Session
  • Works in service layer but fails in the controller or serialization layer
  • Exception occurs during JSON serialization (Jackson triggers lazy getters)
  • Works for some entities but fails for others with lazy associations
bash
org.hibernate.LazyInitializationException:
failed to lazily initialize a collection of role: com.example.User.orders,
could not initialize proxy - no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:607)
    at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:152)
    at com.example.User.getOrders(User.java:45)

Common Causes

  • Entity returned from @Transactional method and accessed in controller
  • Jackson serializing entity with lazy associations after session closed
  • Using @Entity as REST API response without DTO projection
  • Session-per-request pattern with lazy associations in views
  • Caching detached entities and accessing associations later

Step-by-Step Fix

  1. 1.Use JOIN FETCH in queries:
  2. 2.```java
  3. 3.// WRONG - lazy association not loaded
  4. 4.@Query("SELECT u FROM User u WHERE u.id = :id")
  5. 5.User findById(@Param("id") Long id);

// CORRECT - fetch association in the same query @Query("SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders WHERE u.id = :id") User findByIdWithOrders(@Param("id") Long id);

// For multiple associations @Query("SELECT DISTINCT u FROM User u " + "LEFT JOIN FETCH u.orders o " + "LEFT JOIN FETCH u.addresses a " + "WHERE u.id = :id") User findByIdWithAssociations(@Param("id") Long id); ```

  1. 1.Use DTO projections instead of entities:
  2. 2.```java
  3. 3.// DTO - no lazy loading issues
  4. 4.public record UserDTO(Long id, String name, String email, List<String> orderIds) {}

// Query directly to DTO @Query("SELECT new com.example.UserDTO(u.id, u.name, u.email, " + "(SELECT o.id FROM Order o WHERE o.user.id = u.id)) " + "FROM User u WHERE u.id = :id") UserDTO findUserDTOById(@Param("id") Long id); ```

  1. 1.Initialize within transaction boundary:
  2. 2.```java
  3. 3.@Service
  4. 4.public class UserService {

@Transactional(readOnly = true) public User getUserWithOrders(Long id) { User user = userRepository.findById(id).orElseThrow(); // Force initialization while session is open Hibernate.initialize(user.getOrders()); return user; } } ```

  1. 1.Disable lazy loading for specific associations:
  2. 2.```java
  3. 3.@Entity
  4. 4.public class User {
  5. 5.@Id
  6. 6.private Long id;

// Eager loading - always fetch (use sparingly) @OneToMany(mappedBy = "user", fetch = FetchType.EAGER) private List<Order> orders; }

// WARNING: EAGER fetching can cause N+1 and performance issues // Only use for associations that are ALWAYS needed ```

  1. 1.Use Open Session in View (Spring Boot):
  2. 2.```properties
  3. 3.# application.properties - enables OSIV
  4. 4.spring.jpa.open-in-view=true

# WARNING: OSIV can hide performance problems and keep connections open # Only use for simple applications, not recommended for production APIs ```

Prevention

  • Always use DTOs for API responses instead of entities
  • Use JOIN FETCH or EntityGraph for queries that need associations
  • Add @Transactional to service methods that access lazy associations
  • Use Hibernate.isInitialized() to check before accessing lazy properties
  • Never cache entities with uninitialized lazy associations
  • Set spring.jpa.open-in-view=false in production to catch lazy loading issues early
  • Write integration tests that verify entity serialization after session closure