Working with objects in JPA lets us forget the "dirty" work of SQL queries and work with familiar Java objects. But when you fetch related data an important question shows up: when should you load it? JPA gives you two approaches for that: Lazy (lazy) and Eager (eager) fetching.
How does this work in practice?
Imagine an order service where each order has a list of products:
- Eager loading (Eager) — when you request an order, JPA will automatically load all related products. Convenient, but if you have a thousand orders and you only need their IDs you'll end up transferring a ton of unnecessary data.
- Lazy loading (Lazy) — JPA will load only the main order info. Related products are loaded only when you explicitly access them. That's efficient, but you need to be careful about when the data is loaded.
Choosing between Lazy and Eager is always a trade-off between convenience and performance. Often the right choice depends on the exact use case.
Fetch type configurations
JPA offers two ways to fetch related data, and you pick one using the fetch attribute.
The fetch attribute in JPA
@ManyToOne(fetch = FetchType.LAZY)
private User user;
- FetchType.LAZY — lazy loading: data is pulled only when it's actually needed.
- FetchType.EAGER — eager loading: all related data is loaded immediately.
Let's look at a real online store example:
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
@OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
private List<Product> products;
// getters and setters
}
What's happening here:
- Customer info (
Customer) is loaded lazily. Makes sense: you don't always need customer data when you're working with an order. - Products (
Product) are loaded immediately — an order without its product list doesn't make sense!
Differences between Lazy and Eager
| Lazy Loading | Eager Loading | |
|---|---|---|
| When it fetches data | When they're accessed | Right away when the parent object is loaded |
| Performance | Saves resources if related data isn't used | Can lead to excessive queries |
| Implementation complexity | You can hit errors like LazyInitializationException |
Works "out of the box" |
| Usage | Recommended for most operations | Use only if you're sure the data is always needed |
Practice: Lazy fetching in action
Entity example: Customer (Customer) and Orders (Order)
Let's slightly complicate our database app. We already have Customer and Order entities, and we'll implement a one-to-many relationship between a customer and their orders.
Entity Customer
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "customer", fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
// getters and setters
}
Entity Order
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String description;
@ManyToOne(fetch = FetchType.LAZY)
private Customer customer;
// getters and setters
}
We see both sides are set to Lazy. Now let's try to fetch data from the database.
Repositories
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> { }
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> { }
Service class
@Service
public class CustomerService {
@Autowired
private CustomerRepository customerRepository;
public void printCustomerOrders(Long customerId) {
Customer customer = customerRepository.findById(customerId).orElseThrow();
System.out.println("Customer: " + customer.getName());
System.out.println("Orders: " + customer.getOrders());
}
}
The LazyInitializationException
Try calling printCustomerOrders(), and... BAM! An error!
LazyInitializationException tells us that the Hibernate context has already closed the connection to the database, and we can't load lazy data. Why? We're trying to access customer.getOrders() outside of a transaction.
How to avoid this error?
- Use Eager loading. Just change
fetchtoFetchType.EAGER. But that's not always justified. - Manual initialization. Use
Hibernate.initialize()to load lazy data.@Transactional public void printCustomerOrders(Long customerId) { Customer customer = customerRepository.findById(customerId).orElseThrow(); Hibernate.initialize(customer.getOrders()); System.out.println("Customer: " + customer.getName()); System.out.println("Orders: " + customer.getOrders()); } - Or (my favorite): JOIN FETCH. Use a query with
JOIN FETCHto preload the data.@Query("SELECT c FROM Customer c JOIN FETCH c.orders WHERE c.id = :id") Customer findCustomerWithOrders(@Param("id") Long id);And in the method just:
public void printCustomerOrders(Long customerId) { Customer customer = customerRepository.findCustomerWithOrders(customerId); System.out.println("Customer: " + customer.getName()); System.out.println("Orders: " + customer.getOrders()); }
Why this matters
When you're designing complex apps, the fetch type can seriously affect performance. For example, if a system has thousands of entities, eager loading can drown your database in tons of unnecessary queries. On the other hand, a poorly set up lazy strategy can cause a bunch of runtime surprises.
Common mistakes and how to avoid them
- Abusing Eager loading: this is usually a beginner mistake. They think "the more data I load, the better." But if the data isn't used, it's wasted resources.
- Missing a transaction with Lazy loading: make sure lazy data is always loaded within a transaction.
- Request overload with Lazy loading: if lazy data is loaded inside a loop, you can end up with many separate SQL queries. Use
JOIN FETCHto combine them into a single query.
Practical use
In interviews they'll definitely ask about the difference between Lazy and Eager. Knowing how to avoid lazy-related mistakes and when to use eager loading (in a good way) is a great chance to impress.
In real projects where you deal with lots of related data, proper fetch configuration is critical for performance. For example, social networks where users are connected to tons of stuff — friends, posts, comments — require smart choices about fetching.
So let's move on and don't be lazy, so we don't get lazy exceptions!
GO TO FULL VERSION