N+1 queries silently kill your Spring Boot app performance. Learn how to detect and fix them with JOIN FETCH, @EntityGraph, and Hibernate batch fetching - with real code examples.
JOptimize Team
The N+1 query problem is one of the most common - and most damaging - performance issues in Spring Boot applications. What looks like a single database call silently becomes dozens or hundreds of queries at runtime, and by the time you notice, your app is already struggling under load.
The N+1 problem occurs when your code executes 1 query to load a list of entities, then fires N additional queries to load a related association for each entity. If you have 100 orders and load their customers lazily, you get 1 + 100 = 101 queries instead of 1.
@Entity public class Order { @ManyToOne(fetch = FetchType.LAZY) private Customer customer; } // In your service: List<Order> orders = orderRepository.findAll(); // 1 query for (Order order : orders) { System.out.println(order.getCustomer().getName()); // N queries! }
With 100 orders, Hibernate silently fires 101 SQL statements. With 1000 orders, your database gets hammered with 1001 queries for what should be a single JOIN.
The fastest way is to enable SQL logging and count the queries. Add this to application.properties:
spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true logging.level.org.hibernate.type.descriptor.sql=TRACE
For production-grade detection, enable Hibernate Statistics:
spring.jpa.properties.hibernate.generate_statistics=true logging.level.org.hibernate.stat=DEBUG
Check for HHH000117 in your logs - it shows the total query count per session. If you see 50 queries for what should be 1, you have an N+1 problem.
The most direct fix - tell Hibernate to load the association in a single JOIN query:
@Repository public interface OrderRepository extends JpaRepository<Order, Long> { @Query("SELECT o FROM Order o JOIN FETCH o.customer WHERE o.status = :status") List<Order> findByStatusWithCustomer(@Param("status") OrderStatus status); }
This generates a single SQL query with an INNER JOIN, loading both Order and Customer in one round-trip. Use LEFT JOIN FETCH if the association is optional.
Caveat: JOIN FETCH with pagination (Pageable) triggers a HHH90003004 warning and loads everything in memory. For paginated queries with associations, use @EntityGraph instead.
@EntityGraph is the Spring Data JPA-native way to eagerly load associations for a specific query without changing the entity's default fetch type:
@Repository public interface OrderRepository extends JpaRepository<Order, Long> { @EntityGraph(attributePaths = {"customer", "customer.address"}) List<Order> findByStatus(OrderStatus status); // Works correctly with pagination @EntityGraph(attributePaths = {"customer"}) Page<Order> findAll(Pageable pageable); }
For multiple collections, use named entity graphs to avoid MultipleBagFetchException:
@Entity @NamedEntityGraph( name = "Order.withItemsAndPayments", attributeNodes = { @NamedAttributeNode("items"), @NamedAttributeNode("payments") } ) public class Order { ... } // In repository: @EntityGraph("Order.withItemsAndPayments") List<Order> findByCustomerId(Long customerId);
When you can't rewrite the query, batch fetching reduces N+1 to ceil(N/batchSize) + 1 queries:
# application.properties spring.jpa.properties.hibernate.default_batch_fetch_size=25
Or per-association:
@Entity public class Order { @ManyToOne(fetch = FetchType.LAZY) @BatchSize(size = 25) private Customer customer; }
With 100 orders and batch size 25, Hibernate fires 1 + 4 = 5 queries instead of 101. A quick win with zero query rewrites.
SELECT DISTINCT o FROM Order o JOIN FETCH o.itemsMultipleBagFetchException; use @EntityGraph or two separate queriesThe N+1 query problem happens when Hibernate lazily loads associations in a loop. Fix it with JOIN FETCH for simple cases, @EntityGraph for pagination or multiple paths, and Hibernate batch fetching as a quick fallback. Always verify the fix by checking your SQL logs - the query count should drop to 1 or 2.
JOptimize statically analyzes your Spring Boot project and flags every repository method that will trigger an N+1 query at runtime - before you deploy. The IntelliJ plugin highlights the exact line and suggests the correct JOIN FETCH or @EntityGraph fix inline as you code.
Catch N+1 queries before they reach production - free scan, no configuration required.
Master Spring Boot, security, and Java performance with hands-on courses.
JOptimize finds N+1 queries, EAGER collections, and 70+ other issues in your Java codebase — in under 30 seconds.