Diagnose a LazyInitializationException thrown when a controller renders an entity
The endpoint below returns an order with its lines. The repository call succeeds, yet the request fails with `org.hibernate.LazyInitializationException: could not initialize proxy
@Transactional test.
- no Session` the moment the response is serialized. The same code passes inside a
Constraints:
itemsmust stayLAZY— other endpoints load an order without its lines- do not enable
spring.jpa.open-in-view
@Entity
class Order {
@Id Long id;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
List<OrderItem> items = new ArrayList<>();
}
@Service
class OrderService {
@Transactional(readOnly = true)
public Order findOrder(Long id) {
return repo.findById(id).orElseThrow();
}
}
@RestController
class OrderController {
@GetMapping("/orders/{id}")
public Order get(@PathVariable Long id) {
return service.findOrder(id); // ← serialization touches order.getItems()
}
}
Diagnose the cause.
items is a lazy proxy that can only initialize while the persistence context that produced it is open. @Transactional ends when findOrder returns, the session closes, and the serializer touches the proxy outside it — hence no Session. The test passes only because its own @Transactional keeps the session open. Fix it by loading what the caller needs inside the transaction (JOIN FETCH, @EntityGraph) and returning a DTO.
- ✗Blaming the
LAZYmapping itself instead of the closed persistence context - ✗Turning on
open-in-viewand calling it a fix — it hides the N+1 selects behind the view - ✗Returning the detached entity straight from the controller instead of a DTO
- →Why does
Hibernate.initialize(order.getItems())fail outside the transaction too? - →Why is
open-in-viewa poor default in a REST service?
What is happening
repo.findById(id) returns an Order whose items is not an ArrayList but a lazy collection proxy (PersistentBag). It holds a reference to its session and resolves only on first access. The transaction is opened by @Transactional on findOrder and closes exactly when that method returns — and the persistence context closes with it.
The controller receives an already detached entity. Jackson walks its fields, calls getItems(), and the proxy tries to reach its session — which is gone:
org.hibernate.LazyInitializationException: could not initialize proxy
[Order.items#42] - no Session
at org.hibernate.collection.spi.AbstractPersistentCollection.throwLazyInitializationException
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(...)
The test passes because @Transactional on the test method keeps the session open until the test ends — the transaction boundary is simply wider there, the code is not different.
The fix
public interface OrderRepo extends JpaRepository<Order, Long> {
@EntityGraph(attributePaths = "items") // load the lines in the same query
Optional<Order> findWithItemsById(Long id);
}
@Service
class OrderService {
@Transactional(readOnly = true)
public OrderDto findOrder(Long id) {
Order order = repo.findWithItemsById(id).orElseThrow();
return OrderDto.from(order); // ✅ map to a DTO INSIDE the transaction
}
}
Other endpoints keep calling the plain findById and do not pay for the lines — LAZY stays where it is.
❌ spring.jpa.open-in-view=true would stretch the session across rendering and make the exception go away, but not the problem: the lazy access moves into the view layer, where it produces the same N+1 queries, and the connection is held until the last byte of the response.