N+1 Problem

Intermediate
Performance

Loading a list of entities and accessing a lazy association triggers one SELECT per entity; detect with Hibernate statistics or slow query log; fix with JOIN FETCH or entity graphs.

Overview

The N+1 problem is one of the most common and damaging performance issues in JPA/Hibernate applications, and a favourite interview topic. It occurs when you load a collection of N entities and then access a lazy-loaded association on each one. Hibernate issues 1 query to fetch the N entities, then N additional queries — one per entity — to fetch the association, resulting in N+1 total queries. For a list of 500 orders, accessing order.getCustomer() causes 501 SELECT statements instead of 1 or 2. This is invisible in development (small data sets) but causes severe latency and database load in production. The fix is to tell Hibernate to fetch the association in the same query using JOIN FETCH or @EntityGraph.

What Causes N+1

By default, @ManyToOne and @OneToOne are EAGER, and @OneToMany and @ManyToMany are LAZY. The N+1 problem most commonly occurs with LAZY associations when you iterate over a list of entities and touch the lazy proxy, forcing Hibernate to fire individual SELECTs. Even EAGER associations can cause N+1 when using JPQL — the EAGER setting only applies to EntityManager.find(), not JPQL queries.

Java — JPA (Bad Example)
// Entity mapping
@Entity
public class Order {
    @Id Long id;
    String reference;

    @ManyToOne(fetch = FetchType.LAZY)  // lazy = not loaded until accessed
    Customer customer;
}

// ❌ N+1 problem — 1 + N queries
List<Order> orders = em.createQuery("SELECT o FROM Order o", Order.class)
                       .getResultList();                   // 1 SELECT orders

for (Order o : orders) {
    System.out.println(o.getCustomer().getName());        // N SELECT customers
    // "SELECT * FROM customer WHERE id = ?"  fired for EACH order!
}

// With 500 orders → 501 queries total

Fix 1 — JPQL JOIN FETCH

JOIN FETCH instructs Hibernate to load the association in the same SQL query using a JOIN. This is the most direct fix and works with any JPA provider. The downside: if you JOIN FETCH a collection (@OneToMany), you may get a Cartesian product and duplicate parent rows — use DISTINCT or Set instead of List to de-duplicate.

Java — JPQL JOIN FETCH
// ✅ Fix — JOIN FETCH loads customer in the same query
List<Order> orders = em.createQuery(
    "SELECT DISTINCT o FROM Order o JOIN FETCH o.customer",
    Order.class
).getResultList();
// Result: 1 query with JOIN — "SELECT o.*, c.* FROM orders o JOIN customer c ON ..."

// Spring Data JPA equivalent
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {

    @Query("SELECT DISTINCT o FROM Order o JOIN FETCH o.customer")
    List<Order> findAllWithCustomer();
}

Fix 2 — @EntityGraph

@EntityGraph is a declarative way to specify fetch associations per-query without modifying the JPQL. It is cleaner than JOIN FETCH when using Spring Data repository method names and avoids polluting the query with JOIN FETCH clauses.

Java — Spring Data @EntityGraph
// ✅ Fix — @EntityGraph on a repository method
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {

    @EntityGraph(attributePaths = {"customer"})
    List<Order> findAll();                    // loads customer in same query

    @EntityGraph(attributePaths = {"customer", "items", "items.product"})
    Optional<Order> findById(Long id);        // deep graph for detail view
}

// Detecting N+1 — enable Hibernate statistics
spring.jpa.properties.hibernate.generate_statistics=true
spring.jpa.show-sql=true
logging.level.org.hibernate.stat=DEBUG
// Then check: "HikariPool-1 - After acquiring connection" count in logs

Key Points to Remember

  • 1N+1 occurs when accessing a lazy association inside a loop — 1 query for N entities + N queries for the association = N+1 total.
  • 2The problem is invisible in dev (small data) but catastrophic in production; always test with realistic data volumes.
  • 3JOIN FETCH in JPQL is the most direct fix; it loads the association in a single SQL JOIN query.
  • 4@EntityGraph achieves the same result declaratively on Spring Data repository methods without writing JPQL.
  • 5Never JOIN FETCH two collection associations at the same time — this causes a Cartesian product and multiplied rows.
  • 6Enable hibernate.generate_statistics and spring.jpa.show-sql to detect N+1 during development and code review.

Interview Questions

Sign in to ask Aria
1

What is the N+1 problem in Hibernate and how does it occur?

MediumAmazon
2

How do you detect the N+1 problem in a running application?

MediumGoogle
3

What is the difference between JOIN FETCH and @EntityGraph in Hibernate?

MediumFlipkart
4

Why does EAGER fetching in @OneToMany not always prevent the N+1 problem in JPQL?

HardThoughtworks
5

What is a Cartesian product problem and when does it happen with JOIN FETCH?

HardNetflix

Ask Aria about N+1 Problem

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…