When to Use Lazy vs Eager Loading
The choice between LAZY and EAGER is a choice between scalability and development convenience. In modern Java/Spring development, there's a "golden rule": use LAZY by default fo...
Overview
The choice between LAZY and EAGER is a choice between scalability and development convenience. In modern Java/Spring development, there’s a “golden rule”: use LAZY by default for the vast majority of associations.
Junior Level
Basic Rule
Almost always use LAZY. EAGER - only when you know exactly why.
LAZY when:
@OneToManyor@ManyToManyassociation- Related data is not always needed
- Large collections
- Data may be heavy to load
EAGER when:
@ManyToOneand data is always needed- Small, mandatory associations (reference data)
- Data is needed in 100% of parent entity usages
// LAZY - items not always needed
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
// LAZY - user not always needed
@ManyToOne(fetch = FetchType.LAZY)
private User user;
// EAGER makes sense only for small mandatory associations
// UserProfile is a small entity, always displayed with User,
// without it User is incomplete. EAGER is justified here.
@OneToOne(fetch = FetchType.EAGER)
private UserProfile profile; // always needed with User
When LAZY is a Bad Choice
- Batch processing when all related data is needed anyway - better to load immediately via JOIN FETCH
- Small reference tables (1-10 records) - lazy loading overhead exceeds the benefit
- Entities that are ALWAYS needed together - two separate queries are slower than one JOIN
Middle Level
Practical Recommendations
@ManyToOne -> LAZY (almost always, despite the EAGER default)
@OneToMany -> LAZY (default, don't change)
@OneToOne -> LAZY (often forgotten to override)
@ManyToMany -> LAZY (default, don't change)
Why Override @ManyToOne to LAZY
@Entity
public class Order {
// Default is EAGER, but we override
@ManyToOne(fetch = FetchType.LAZY)
private User user;
}
// When loading Order without JOIN:
// Without LAZY: SELECT o.*, u.* FROM orders o JOIN users u ON ...
// With LAZY: SELECT o.* FROM orders o (user loads only on access)
Examples of Correct Decisions
// LAZY for collection
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
// LAZY for @ManyToOne
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
// LAZY for @OneToOne
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "address_id")
private Address address;
// Load when needed - via JOIN FETCH
@Query("SELECT DISTINCT o FROM Order o " +
"JOIN FETCH o.user " +
"JOIN FETCH o.items " +
"WHERE o.id = :id")
Order findByIdComplete(@Param("id") Long id);
Common Mistakes
// EAGER for large collections
@OneToMany(fetch = FetchType.EAGER)
private List<Order> orders;
// Loads ALL orders -> performance degradation
// EAGER "just in case"
@ManyToOne(fetch = FetchType.EAGER)
private Category category;
// Even when category is not displayed - extra JOIN
Senior Level
Architectural Trade-offs
| LAZY | EAGER |
|---|---|
| More flexible and scalable | Simpler to develop |
| Need to think about loading | Always available |
| Can cause LazyInitializationException | Can cause N+1 problem |
| Dynamic loading | Static loading |
| Suitable for microservices | Suitable for monoliths |
Senior Developer Strategy: “LAZY + Dynamic Loading”
Instead of statically declaring FetchType in the Entity, use dynamic loading:
1. Globally: LAZY for all associations
2. Locally: FETCH when needed in a specific query
- JPQL: JOIN FETCH
- Spring Data JPA: @EntityGraph
- Criteria API: root.fetch("association")
Why “LAZY by Default” is Correct
If you declare an association as EAGER:
-> You can NEVER make it lazy in a specific query
-> Hibernate will still fetch the data
If you declare an association as LAZY:
-> You can "turn it into" EAGER at any time via JOIN FETCH
-> LAZY provides maximum flexibility
DTO Projection Instead of EAGER
// Instead of EAGER loading - use DTOs for APIs
@Query("""
SELECT new com.example.OrderDto(
o.id, o.date, o.status, u.name, u.email
)
FROM Order o JOIN o.user u
WHERE o.id = :id
""")
OrderDto findOrderDto(@Param("id") Long id);
// Advantages:
// 1. One query instead of two
// 2. Less data in memory
// 3. No serialization problems
// 4. Controlled API contract
Microservice Architecture
In microservices with a shared DB (not recommended, but it happens) LAZY is especially important because extra data = extra load on a shared resource. In proper microservices with separate DBs, this question is resolved at the API contract level.
Production Experience
// Spring Data JPA with @EntityGraph
public interface OrderRepository extends JpaRepository<Order, Long> {
@EntityGraph(attributePaths = {"user", "items"})
Optional<Order> findByIdWithDetails(Long id);
@EntityGraph(attributePaths = {"user"})
List<Order> findByUserId(Long userId);
// Without EntityGraph - only Order without associations
List<Order> findByStatus(String status);
}
Best Practices
LAZY by default for ALL associations
Override @ManyToOne and @OneToOne to LAZY
JOIN FETCH when data is needed
@EntityGraph for dynamic loading
DTO projection for read-only/API
Monitor DB queries
EAGER for collections
EAGER "just in case"
Solving LazyInitializationException via EAGER
Loading entities for simple read-only operations
Summary for Senior
- EAGER - static and intrusive, cannot be cancelled
- LAZY - dynamic and flexible, can load when needed
- Override default EAGER associations (
OneToOne,ManyToOne) to LAZY - Solve
LazyInitializationExceptionthrough proper transaction design or Entity Graphs, not by switching to EAGER
Interview Cheat Sheet
Must know:
- Golden rule: LAZY by default for ALL associations
- Override @ManyToOne and @OneToOne to LAZY (their default is EAGER)
- EAGER cannot be cancelled in a query, LAZY can be loaded via JOIN FETCH
- LAZY provides maximum flexibility - dynamic loading when needed
- DTO projection for APIs instead of loading full entities
- In microservices, LAZY is especially important to reduce DB load
Frequent follow-up questions:
- When is EAGER justified? Small mandatory associations (reference data), data needed in 100% of cases
- Why override @ManyToOne to LAZY? Even when user is not needed - extra JOIN without LAZY
- What to do about LazyInitializationException? Proper transaction design, Entity Graphs, DTO - NOT switching to EAGER
Red flags (DO NOT say):
- “EAGER for @ManyToOne - it’s the default, I don’t touch it” - needs overriding to LAZY
- “I solve LazyInitializationException via EAGER” - creates N+1
- “LAZY only for collections” - LAZY for all associations including @ManyToOne
Related topics:
- [[2. What is the Difference Between Lazy and Eager Loading]]
- [[4. What is LazyInitializationException and How to Avoid It]]
- [[5. What Fetch Strategies Exist in Hibernate]]
- [[29. What is Projection in JPA]]