Lazy vs Eager Fetching in Hibernate: Performance, Best Practices, and Pitfalls Explained

Illustration for Lazy vs Eager Fetching in Hibernate: Performance, Best Practices, and Pitfalls Explained
By Last updated:

Fetching in Hibernate determines when related entities are loaded from the database. This choice directly affects application performance, memory usage, and user experience. Misusing fetching strategies can lead to common problems like the N+1 select issue or unnecessary data loads.

Think of it like ordering food at a restaurant:

  • Lazy Fetching = You order items only when you actually need them.
  • Eager Fetching = You order everything upfront, even if you might not eat all of it.

This tutorial covers Lazy vs Eager fetching in Hibernate with code examples, configurations, pitfalls, and best practices for production-ready applications.


Core Definition and Purpose

  • Lazy Fetching: Entities or collections are loaded on demand, when explicitly accessed.
  • Eager Fetching: Entities or collections are loaded immediately with the parent entity.

Hibernate defaults to lazy fetching for collections and eager fetching for single-valued associations unless specified otherwise.


Setup and Configuration

Example Entities

@Entity
@Table(name = "authors")
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<>();

    // getters and setters
}

@Entity
@Table(name = "books")
public class Book {

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

    private String title;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "author_id")
    private Author author;

    // getters and setters
}
  • @OneToMany is lazy by default.
  • @ManyToOne is eager by default.
  • You can override using fetch = FetchType.LAZY or fetch = FetchType.EAGER.

CRUD Examples with Fetching

Create Entities

Author author = new Author();
author.setName("George R. R. Martin");

Book book1 = new Book();
book1.setTitle("A Game of Thrones");
book1.setAuthor(author);

Book book2 = new Book();
book2.setTitle("A Clash of Kings");
book2.setAuthor(author);

author.getBooks().add(book1);
author.getBooks().add(book2);

session.persist(author);
transaction.commit();

Lazy Fetching Example

Author author = session.get(Author.class, 1L);
System.out.println(author.getName());

// Books are fetched ONLY when accessed
for (Book book : author.getBooks()) {
    System.out.println(book.getTitle());
}

Generated SQL:

select * from authors where id = 1;
select * from books where author_id = 1;

Eager Fetching Example

Author author = session.get(Author.class, 1L);
System.out.println(author.getBooks()); // Already fetched

Generated SQL:

select * from authors a
left join books b on a.id = b.author_id
where a.id = 1;

Hibernate Session and Transaction Usage

SessionFactory factory = new Configuration().configure().buildSessionFactory();
Session session = factory.openSession();
Transaction transaction = session.beginTransaction();

// Perform CRUD with lazy/eager fetching
transaction.commit();
session.close();

Querying with Hibernate

HQL with Fetch Joins (to avoid N+1 problem)

String hql = "select a from Author a join fetch a.books where a.id = :id";
Author author = session.createQuery(hql, Author.class)
                       .setParameter("id", 1L)
                       .getSingleResult();

Performance Considerations

  • Lazy Loading saves memory and improves performance when related data isn’t always needed.
  • Eager Loading is better when related data is always required (e.g., dashboard queries).
  • Misusing eager fetching can load massive datasets unnecessarily, slowing performance.

N+1 Select Problem

Occurs when lazy loading is misused:

List<Author> authors = session.createQuery("from Author", Author.class).list();
for (Author a : authors) {
    System.out.println(a.getBooks().size()); // triggers a query per author
}

Solution → Use fetch joins or batch fetching.


Real-World Integration with Spring Boot

@Repository
public interface AuthorRepository extends JpaRepository<Author, Long> {

    @Query("select a from Author a join fetch a.books")
    List<Author> findAllWithBooks();
}

Spring Data JPA integrates seamlessly with Hibernate fetching strategies.


Anti-Patterns and Common Pitfalls

  • Using Eager Fetching everywhere → Leads to performance bottlenecks.
  • Lazy Initialization Exception → Accessing lazy-loaded collections outside of a session.
  • Ignoring Fetch Joins → Causes N+1 select problems.

Best Practices

  • Use Lazy Fetching by default.
  • Use Eager Fetching only when the data is always required.
  • Use fetch joins in queries to optimize performance.
  • Prefer DTO projections for API responses.

📌 Hibernate Version Notes

  • Hibernate 5

    • Legacy APIs for SessionFactory.
    • Fetching defaults remain the same.
  • Hibernate 6

    • Transitioned to Jakarta Persistence (jakarta.persistence.*) package.
    • Improved SQL generation and new query API.
    • Fetch strategies remain similar but optimized for modern SQL dialects.

Conclusion and Key Takeaways

  • Lazy and Eager fetching directly affect application performance.
  • Default: Collections = Lazy, Single-Valued = Eager.
  • Use fetch joins to avoid N+1 problems.
  • Always design fetching strategies based on real-world use cases.

FAQ

1. What’s the difference between Hibernate and JPA?
Hibernate is an ORM implementation, while JPA is a specification.

2. How does Hibernate caching improve performance?
By storing frequently accessed data in memory instead of hitting the database repeatedly.

3. What are the drawbacks of eager fetching?
It loads unnecessary data, increasing memory usage and slowing queries.

4. How do I solve the N+1 select problem in Hibernate?
Use join fetch, batch fetching, or second-level cache.

5. Can I use Hibernate without Spring?
Yes, Hibernate can work as a standalone ORM without Spring.

6. What’s the best strategy for inheritance mapping?
@Inheritance(strategy = InheritanceType.JOINED) is often recommended for normalized DB design.

7. How does Hibernate handle composite keys?
By using @Embeddable and @EmbeddedId.

8. How is Hibernate 6 different from Hibernate 5?
Hibernate 6 adopts Jakarta Persistence namespace and introduces enhanced query APIs.

9. Is Hibernate suitable for microservices?
Yes, but careful use of fetching and DTO projections is essential for scalability.

10. When should I not use Hibernate?
When raw SQL or lightweight frameworks (like MyBatis) better fit performance-sensitive scenarios.