Lazy vs Eager Loading - When to Load What

Site Admin · 11 Sep 2026 · 7 views

Lazy vs Eager Loading - When to Load What

When an entity has relationships to other entities, the ORM must decide when to load the related data. Loading everything upfront wastes memory and bandwidth. Loading too little causes cascading queries. JPA gives you control over this with the FetchType attribute on relationship annotations.

Eager Loading

Eager loading tells Hibernate to load the related entity or collection immediately, in the same query (or a batch of queries) as the parent. When you load an Author, the books collection is fetched at the same time:

@Entity
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "author", fetch = FetchType.EAGER)
    private List<Book> books = new ArrayList<>();
}

Eager fetching is convenient but dangerous. Loading an Author eagerly fetches every Book, and if Book has eager collections of its own, the load cascades. This is the classic N+1 problem disguised under the name "eager." For a simple author-book relationship it may be acceptable, but in a large entity graph it becomes a performance disaster.

Lazy Loading

Lazy loading is the default for collections in JPA. Hibernate does not load the related data until you first access it. The collection is replaced by a proxy that triggers a database query on first use:

@Entity
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
    private List<Book> books = new ArrayList<>();
}

When you load an Author, only the author row is fetched. The books list is a Hibernate proxy. The moment you call author.getBooks() or iterate over it, Hibernate fires a SELECT for the books. If you never access the list, no query runs.

The LazyInitializationException

The most common pitfall with lazy loading happens when you access the proxy outside the persistence context - typically in a view layer after the transaction has closed:

@Transactional(readOnly = true)
public Author getAuthor(Long id) {
    return entityManager.find(Author.class, id);
}

// In the controller or view - transaction is closed
Author author = authorService.getAuthor(1L);
List<Book> books = author.getBooks(); // LazyInitializationException!

The transaction closes when getAuthor() returns. The proxy cannot issue a query without an active EntityManager. You can fix this with JOIN FETCH in the query, with Open Session in View, or by loading all needed data within the transaction boundary.

When to Use Each

Use lazy loading by default. Only switch to eager when you are certain the related data is always needed with the parent. For relationships where you sometimes need the related data and sometimes do not, keep lazy and use JOIN FETCH or @EntityGraph on a per-query basis to load it when needed.

Real-World Scenario

An e-commerce product catalog has Products with Categories, Reviews, and Suppliers. A product listing page needs only the product name and price - loading reviews and suppliers eagerly for every product on the page would execute hundreds of unnecessary queries. Keep all relationships lazy. On the product detail page, use a JOIN FETCH query to load the product with its reviews in a single query.

Key Points

  • Eager loading fetches related data immediately with the parent entity, regardless of whether it is used.
  • Lazy loading defers fetching until the relationship is first accessed, saving resources.
  • LazyInitializationException occurs when accessing a lazy proxy outside an open transaction.
  • Lazy is the default and preferred strategy for collections in most applications.
  • Use JOIN FETCH or @EntityGraph to selectively load lazy data within the transaction boundary.
Share this post:

Comments (0)

Please login or register to comment.