Lazy vs Eager Loading in JPA — FetchType Explained
Fast recall
Quick revision
Core answer
Whether you understand JPA fetch strategies, their defaults per association type, and the performance trade-offs (N+1 vs over-fetching) that flow from them
Switching everything to EAGER after one LazyInitializationException — a panic fix that loads too much data on every query and hides the real issue
Name the N+1 problem and show that JOIN FETCH or @EntityGraph fixes it per query while the entity stays LAZY — fine-grained control instead of a global hammer
Interview-ready guide
Interview answer
Loading an Order with lazy items may leave a persistent collection uninitialized until code accesses it in an open persistence context. An eager customer must be available, but Hibernate can obtain it through the original SQL or another SELECT. Making an association eager therefore does not guarantee removal of N+1. A list endpoint and a detail endpoint need different graphs, so query-specific fetching is usually clearer than one global eager model.
For example, an order-list endpoint usually should not load every order item. I keep that collection lazy and fetch the exact projection or join needed for the endpoint instead of making it globally eager.
Lazy vs Eager Loading in JPA — FetchType Explained
Direct Answer
LAZY allows an association to be fetched later when it is accessed; EAGER requires it to be available when the entity is returned to the application. EAGER does not promise one join or one SQL statement—the provider may use secondary selects. JPA defaults to LAZY for @OneToMany/@ManyToMany and EAGER for @ManyToOne/@OneToOne; LAZY is a hint under the specification.
Code Example
import jakarta.persistence.*;
import java.util.*;
@Entity
public class Order {
@Id @GeneratedValue private Long id;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY) // explicit LAZY
private List<OrderItem> items = new ArrayList<>();
@ManyToOne(fetch = FetchType.EAGER) // explicit EAGER
private Customer customer;
public Customer getCustomer() { return customer; }
public List<OrderItem> getItems() { return items; }
}
@org.springframework.stereotype.Service
class OrderService {
@org.springframework.transaction.annotation.Transactional
public void processOrder(Long orderId) {
Order order = orderRepository.findById(orderId).orElseThrow();
// customer already loaded (EAGER) — no extra query
System.out.println(order.getCustomer().getName());
// items NOT loaded yet (LAZY) — accessing triggers a SELECT now
order.getItems().forEach(item -> System.out.println(item.getProduct()));
}
}
Common Mistakes & Pitfalls
- Setting everything to EAGER — one LazyInitializationException, and the panic fix is global
FetchType.EAGER, which degrades every query by always loading unused data through expensive JOINs. - Not knowing the defaults — forgetting that @ManyToOne is EAGER causes surprise N+1 when you loop over Orders and each loads its Customer separately.
- Ignoring transaction boundaries — accessing lazy collections outside
@Transactionalthrows LazyInitializationException, then it's 'fixed' wrong with Open Session In View instead of proper transaction scoping.
Check your understanding
Answer these without looking back at the article.
- What is LazyInitializationException and how do you fix it?
- What is the N+1 problem in JPA?
- What is @EntityGraph and how does it help with lazy loading?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.