N+1 Query Problem in JPA
Fast recall
Quick revision
Core answer
Awareness of JPA's most common performance pitfall and how lazy loading can cause hidden database load
Not knowing about N+1 or assuming lazy loading is always optimal without understanding its database impact
Mention both JPA solutions (JOIN FETCH in JPQL, @EntityGraph) and explain that JDBC's single JOIN is the clearest fix
Interview-ready guide
Interview answer
Loading 100 orders and running SELECT ... FROM items WHERE order_id = ? one hundred times is N+1 even when every call is handwritten JDBC. A JOIN can return orders and items together, while one second query WHERE order_id IN (...) can fetch all items without repeating parent columns. JPA offers fetch joins, entity graphs, projections, and batch fetching for the same set-oriented goal.
For example, loading 50 orders and then lazily reading each customer can produce 51 queries. I verify the count in SQL metrics, then use a projection, fetch join, entity graph, or batching according to the result shape and pagination needs.
N+1 Query Problem in JPA
Direct Answer
N+1 means one query loads N parent rows and code then performs up to N more queries for related rows. Lazy JPA associations often reveal it, but JDBC code can create the same pattern by executing a child query inside a parent loop. Avoid it with set-based retrieval such as an appropriate join, a projection, one IN query, or bounded batching.
Code Example
// Entity with lazy loading (default)
@Entity
class Customer {
@Id private Long id;
private String name;
@OneToMany(mappedBy = "customer", fetch = FetchType.LAZY) // Lazy is default
private List<Order> orders;
}
// Service code that triggers N+1 problem
public void printCustomerOrders() {
var customers = customerRepository.findAll(); // 1 query: SELECT * FROM customers
for (var customer : customers) {
System.out.println(customer.getName());
var orderCount = customer.getOrders().size(); // N queries: SELECT * FROM orders WHERE customer_id = ?
System.out.println("Orders: " + orderCount);
}
// Total: 1 + N queries (N+1)
}
// Solution 1: JOIN FETCH in JPQL
@Query("SELECT c FROM Customer c JOIN FETCH c.orders")
List<Customer> findAllWithOrders(); // Single query with JOIN
// Solution 2: JDBC approach - explicit JOIN
public List<CustomerWithOrders> findCustomersWithOrdersJdbc() {
var sql = "SELECT c.id, c.name, o.id, o.total FROM customers c LEFT JOIN orders o ON c.id = o.customer_id";
return jdbcTemplate.query(sql, new CustomerOrderRowMapper()); // One query
}
Common Mistakes & Pitfalls
- Not enabling SQL logging during development: Freshers never see the hidden queries JPA generates, so they don't realize N+1 is happening until production slows down. Always develop with spring.jpa.show-sql=true to see real query patterns.
- Assuming lazy loading is always better: Beginners think lazy is optimal without understanding the trade-off — lazy loading avoids upfront data fetch but causes multiple queries if you actually need the data. Sometimes eager loading is the right choice.
- Using FetchType.EAGER globally: When freshers learn about N+1, they switch everything to EAGER, which causes the opposite problem — always loading data even when not needed. The solution is context-specific: use lazy by default, then add JOIN FETCH or @EntityGraph for queries that need related data.
Check your understanding
Answer these without looking back at the article.
- How does @EntityGraph differ from JOIN FETCH in JPA?
- How do you detect N+1 queries in a Spring Boot application?
- What is eager loading vs lazy loading in JPA?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.