Introduction
Java applications that use JPA and Hibernate often handle complex object relationships, such as one-to-many and many-to-one mappings. One of the most critical decisions developers make is choosing between lazy loading and eager loading. While both options functionally work, they have a significant impact on application performance, database load, memory usage, and scalability. This article explains JPA lazy and eager loading in simple words, compares their performance impact, and helps you decide when to use each approach in real-world Java applications.
What Is Fetching in JPA?
Fetching refers to how related entities are loaded from the database when a parent entity is retrieved. JPA provides two fetch strategies:
Lazy loading
Eager loading
The fetch strategy controls when associated data is loaded, not what data exists.
Lazy Loading Explained
Lazy loading means related entities are not loaded immediately. They are fetched only when they are actually accessed in code.
Example: Lazy Loading Mapping
@Entity
public class Order {
@Id
private Long id;
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
private List<OrderItem> items;
}
In this case, OrderItem data is not loaded until getItems() is called.
How Lazy Loading Works Internally
Hibernate creates a proxy object instead of loading the actual data. When the proxy is accessed, an additional SQL query is triggered.
This behavior helps reduce unnecessary data loading but can also introduce performance issues if not handled carefully.
Performance Benefits of Lazy Loading
Lazy loading improves performance by:
Reducing initial query size
Lowering memory usage
Loading only required data
Improving response time for simple queries
Lazy loading is especially useful in large enterprise systems with complex object graphs.
Common Problems with Lazy Loading
N+1 Query Problem
One of the biggest issues with lazy loading is the N+1 query problem.
Example Scenario
List<Order> orders = orderRepository.findAll();
for (Order order : orders) {
System.out.println(order.getItems().size());
}
This results in:
This can severely degrade performance.
Eager Loading Explained
Eager loading means related entities are loaded immediately along with the parent entity.
Example: Eager Loading Mapping
@Entity
public class Order {
@Id
private Long id;
@OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
private List<OrderItem> items;
}
Here, Order and OrderItem are loaded in a single query or join.
Performance Impact of Eager Loading
Eager loading can:
While it avoids extra queries, it may overload the database with large joins.
Lazy vs Eager Loading: Performance Comparison
Lazy Loading
Eager Loading
Choosing the wrong strategy can hurt performance.
FetchType Defaults in JPA
JPA provides default fetch types:
@ManyToOne → EAGER
@OneToMany → LAZY
@ManyToMany → LAZY
Understanding defaults is critical to avoid unexpected performance issues.
Using Fetch Joins for Better Performance
Fetch joins allow loading related entities only when needed.
Example: Fetch Join Query
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.id = :id")
Order findOrderWithItems(@Param("id") Long id);
This approach combines the benefits of lazy loading with optimized queries.
Entity Graphs for Controlled Fetching
Entity graphs allow defining fetch behavior dynamically.
Example
@EntityGraph(attributePaths = {"items"})
Optional<Order> findById(Long id);
Entity graphs improve readability and performance control.
Impact on Memory and Application Scalability
Lazy loading helps scale applications by reducing memory usage, especially in:
REST APIs
Microservices
High-traffic systems
Eager loading may cause:
Lazy Loading and Serialization Issues
Lazy loading often causes serialization problems when returning entities as JSON.
Common Error
Recommended Solutions
Use DTOs
Use fetch joins
Use projection queries
Best Practices for Lazy and Eager Loading
Prefer lazy loading by default
Avoid eager loading on collections
Use fetch joins for required data
Monitor SQL queries
Use DTO-based projections
Avoid returning entities directly from APIs
Real Enterprise Performance Example
In a large e-commerce system:
Lazy loading reduces order list API response time
Fetch joins load details only on order-detail screens
DTOs prevent serialization issues
Controlled fetching improves database performance under heavy traffic
Conclusion
Lazy and eager loading in JPA directly affect database queries, memory usage, and application scalability. Lazy loading improves performance by loading data only when required, while eager loading simplifies access at the cost of heavier queries and higher memory usage. By understanding their trade-offs and using techniques like fetch joins, entity graphs, and DTO projections, developers can build efficient, scalable, and high-performance Java applications that handle large datasets without unnecessary overhead.