Introduction
Hibernate's lazy loading defers database queries until collection or association properties are accessed. When the entity is accessed outside its loading transaction (after the session is closed), lazy properties throw LazyInitializationException: could not initialize proxy - no Session. This is the most common Hibernate error in production because entities are often passed to view layers, serialized to JSON, or sent to message queues after the transaction completes. The fix requires either initializing lazy properties within the transaction, using join fetch in queries, or configuring session management carefully.
Symptoms
org.hibernate.LazyInitializationException: could not initialize proxy [com.example.Order#42] - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:171)Or:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.example.User.orders, could not initialize proxy - no SessionCommon Causes
- Entity accessed after transaction commit: Session closed before lazy property access
- JSON serialization of entity: Jackson tries to serialize lazy collections after session close
- Detached entity passed to service layer: Entity loaded in one transaction, accessed in another
- OpenSessionInView disabled: Spring Boot 3+ disables OSIV by default
- Collection not initialized in query: Query returns entity but not its collections
- N+1 query pattern hidden by lazy loading: Each entity access triggers separate query
Step-by-Step Fix
Step 1: Use JOIN FETCH in queries
```java public interface OrderRepository extends JpaRepository<Order, Long> {
// Eagerly fetch order items in a single query @Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id") Optional<Order> findByIdWithItems(@Param("id") Long id);
// Fetch multiple associations @Query("SELECT DISTINCT o FROM Order o " + "LEFT JOIN FETCH o.items i " + "LEFT JOIN FETCH o.customer c " + "WHERE o.status = :status") List<Order> findByStatusWithDetails(@Param("status") String status); } ```
Step 2: Initialize collections within transaction
```java @Service @Transactional(readOnly = true) public class OrderService {
@PersistenceContext private EntityManager em;
public OrderDTO getOrderDetails(Long orderId) { Order order = em.find(Order.class, orderId);
// Initialize lazy collections while session is open Hibernate.initialize(order.getItems()); Hibernate.initialize(order.getCustomer());
// Convert to DTO before returning return OrderDTO.from(order); } } ```
Step 3: Use DTO projection instead of entities
```java // Interface-based projection (generates efficient SQL) public interface OrderSummary { Long getId(); String getCustomerName(); int getItemCount(); BigDecimal getTotal(); }
public interface OrderRepository extends JpaRepository<Order, Long> { @Query("SELECT o.id as id, c.name as customerName, " + "SIZE(o.items) as itemCount, o.total as total " + "FROM Order o JOIN o.customer c WHERE o.id = :id") OrderSummary findOrderSummary(@Param("id") Long id); }
// No lazy loading issues -- projection returns only requested data ```
Prevention
- Use DTO projections instead of returning entities from service layer
- Always use JOIN FETCH for associations needed in the response
- Initialize lazy collections with Hibernate.initialize() before transaction ends
- Enable OSIV (spring.jpa.open-in-view=true) only for simple applications, not APIs
- Add @Transactional to service methods that access lazy properties
- Use EntityGraph for dynamic fetch plans when different endpoints need different data
- Never serialize Hibernate entities directly to JSON responses