<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Backend Dev]]></title><description><![CDATA[Backend Dev]]></description><link>https://ayusharma.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Backend Dev</title><link>https://ayusharma.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Fri, 25 Sep 2026 11:06:26 GMT</lastBuildDate><atom:link href="https://ayusharma.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[N+1 query problem: Why it happens and How  to fix it]]></title><description><![CDATA[If you’ve worked with Spring Boot and Hibernate for a while, you’ve probably heard about the N+1 query problem.
It sounds complicated at first, but the idea is actually pretty simple:

You make one da]]></description><link>https://ayusharma.hashnode.dev/n-1-query-problem-why-it-happens-and-how-to-fix-it</link><guid isPermaLink="true">https://ayusharma.hashnode.dev/n-1-query-problem-why-it-happens-and-how-to-fix-it</guid><category><![CDATA[N+1]]></category><category><![CDATA[Springboot]]></category><dc:creator><![CDATA[Ayush Sharma]]></dc:creator><pubDate>Sat, 12 Sep 2026 12:09:28 GMT</pubDate><content:encoded><![CDATA[<p>If you’ve worked with Spring Boot and Hibernate for a while, you’ve probably heard about the <strong>N+1 query problem</strong>.</p>
<p>It sounds complicated at first, but the idea is actually pretty simple:</p>
<blockquote>
<p>You make one database query to get your parent records, and then Hibernate makes another query for each parent record to load its related data.</p>
</blockquote>
<p>The problem usually isn’t visible in your Java code. Everything may look perfectly clean while Hibernate quietly executes dozens or even hundreds of SQL queries behind the scenes.</p>
<p>Let’s understand what actually happens and, more importantly, how to fix it.</p>
<hr />
<h2>First, a Quick Look at ORM</h2>
<p>Before ORMs became popular, applications often interacted with databases by writing SQL queries directly.</p>
<p>For example:</p>
<pre><code class="language-sql">SELECT * FROM departments;
</code></pre>
<p>Then the application would manually map the result to Java objects.</p>
<p>This works, but as applications grow, managing SQL and object mapping can become tedious.</p>
<p>That’s where <strong>Object-Relational Mapping (ORM)</strong> comes in.</p>
<p>In the Spring Boot ecosystem, <strong>Hibernate</strong> is the most commonly used JPA implementation. Instead of writing SQL for every operation, we work with Java entities and relationships, and Hibernate generates the SQL for us.</p>
<p>For example:</p>
<pre><code class="language-java">@Entity 
public class Department { 

    @Id 
    private Long id; 
    
    private String name; 

    @OneToMany(mappedBy = "department") 
    private List&lt;Employee&gt; employees; 
}
</code></pre>
<p>This is convenient because we can work with:</p>
<pre><code class="language-java">department.getEmployees();
</code></pre>
<p>instead of manually writing another SQL query.</p>
<p>But that convenience can sometimes hide what’s happening at the database level.</p>
<p>And that’s where N+1 comes in.</p>
<hr />
<h2>What Exactly Is the N+1 Query Problem?</h2>
<p>Let’s say our application has two tables:</p>
<p><strong>Department</strong></p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>Engineering</td>
</tr>
<tr>
<td>2</td>
<td>Finance</td>
</tr>
<tr>
<td>3</td>
<td>HR</td>
</tr>
</tbody></table>
<p><strong>Employee</strong></p>
<table>
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>department_id</th>
</tr>
</thead>
<tbody><tr>
<td>101</td>
<td>John</td>
<td>1</td>
</tr>
<tr>
<td>102</td>
<td>Sarah</td>
<td>1</td>
</tr>
<tr>
<td>103</td>
<td>Mike</td>
<td>2</td>
</tr>
<tr>
<td>104</td>
<td>Alex</td>
<td>3</td>
</tr>
</tbody></table>
<p>And our entities have a relationship like this:</p>
<pre><code class="language-java">@Entity public class Department { 

    @Id 
    private Long id; 
    
    private String name; 
    
    @OneToMany(mappedBy = "department") 
    private List employees; 
}
</code></pre>
<p>Now imagine we write:</p>
<pre><code class="language-java">List departments = departmentRepository.findAll();
</code></pre>
<p>Hibernate initially executes something similar to:</p>
<pre><code class="language-sql">SELECT * FROM department;
</code></pre>
<p>So far, so good.</p>
<p>But then suppose we do this:</p>
<pre><code class="language-sql">for (Department department : departments) { 
    System.out.println(department.getEmployees()); 
}
</code></pre>
<p>Because employees is lazy-loaded, Hibernate may now execute another query for every department:</p>
<pre><code class="language-sql">SELECT * FROM employee WHERE department_id = 1; 
SELECT * FROM employee WHERE department_id = 2; 
SELECT * FROM employee WHERE department_id = 3;
</code></pre>
<p>So if we had <strong>50 departments</strong>, we could end up with:</p>
<p>1 query → fetch 50 departments 50 queries → fetch employees for each department Total = 51 queries</p>
<p>That’s the <strong>N+1 problem</strong>.</p>
<p>The 1 represents the initial query, while N represents the additional queries generated for the N parent records.</p>
<hr />
<h2>Why Is This a Problem?</h2>
<p>One query doesn’t sound scary.</p>
<p>Even ten queries might not immediately cause a problem.</p>
<p>But imagine an API endpoint that returns 500 departments.</p>
<p>Instead of:</p>
<pre><code class="language-plaintext">1 database query
</code></pre>
<p>you could end up with:</p>
<pre><code class="language-plaintext">1 + 500 = 501 queries
</code></pre>
<p>And this is happening during a single API request.</p>
<p>Now imagine multiple users hitting the endpoint simultaneously.</p>
<p>The application starts putting unnecessary pressure on the database:</p>
<pre><code class="language-plaintext">API Request 
    | 
    v 
Spring Boot 
    | 
    v 
Hibernate 
    | 
    +---- Query 1 
    | 
    +---- Query 2 
    | 
    +---- Query 3 
    | 
    +---- Query 4 
    | 
    ... 
    | 
    +---- Query N 
    | 
    v 
Database
</code></pre>
<p>The result can be:</p>
<ul>
<li><p>Increased database load</p>
</li>
<li><p>Higher API response times</p>
</li>
<li><p>More network round trips</p>
</li>
<li><p>Poor scalability</p>
</li>
<li><p>Connection pool pressure</p>
</li>
<li><p>Performance degradation under load</p>
</li>
</ul>
<p>The frustrating part is that the Java code itself may look completely reasonable.</p>
<hr />
<h2>How Does Lazy Loading Contribute to N+1?</h2>
<p>This is where things get interesting.</p>
<p>For collections such as <em>@OneToMany</em>, Hibernate generally uses lazy loading by default.</p>
<p>That means Hibernate doesn’t immediately load all employees when it loads a department.</p>
<p>Instead, it waits until you actually access:</p>
<pre><code class="language-java">department.getEmployees();
</code></pre>
<p>This is usually a good thing.</p>
<p>You don’t want Hibernate loading every related object when you don’t need it.</p>
<p>The problem occurs when you load many parent entities and then access the lazy relationship for each one.</p>
<p>For example:</p>
<pre><code class="language-java">List departments = departmentRepository.findAll(); 

for (Department department : departments) {                           department.getEmployees().size(); 
}
</code></pre>
<p>This can trigger the classic:</p>
<pre><code class="language-plaintext">1 + N
</code></pre>
<p>query pattern.</p>
<p>So <strong>lazy loading itself isn’t bad</strong>.</p>
<p>The problem is how the relationship is accessed.</p>
<hr />
<h2>How Can We Fix the N+1 Problem?</h2>
<p>There isn’t one universal solution.</p>
<p>The right approach depends on what data your API actually needs.</p>
<p>Here are some of the most useful approaches.</p>
<hr />
<h3>1. Use DTOs for API Responses</h3>
<p>For REST APIs, DTOs are often one of the cleanest approaches.</p>
<p>Instead of returning your entire entity graph, define exactly what the API needs.</p>
<p>For example:</p>
<pre><code class="language-java">public record DepartmentDTO( Long id, String name ) {}
</code></pre>
<p>Then query only the required fields.</p>
<p>For example:</p>
<pre><code class="language-java">@Query(""" 
    SELECT new com.example.dto.DepartmentDTO(d.id, d.name) 
    FROM Department d 
""") 
List findDepartmentDTOs();
</code></pre>
<p>This prevents Hibernate from loading relationships that your API doesn’t actually need.</p>
<p><strong>Why I like this approach</strong></p>
<p>DTOs make the API contract explicit.</p>
<p>You’re essentially saying:</p>
<blockquote>
<p>“I need these three fields.”</p>
</blockquote>
<p>instead of:</p>
<blockquote>
<p>“Give me the entire Department entity and whatever relationships happen to be attached to it.”</p>
</blockquote>
<p>For real-world REST APIs, this can also help prevent:</p>
<ul>
<li><p>Accidental lazy-loading queries</p>
</li>
<li><p>Large response payloads</p>
</li>
<li><p>Circular entity serialization</p>
</li>
<li><p>Exposing internal entity structure</p>
</li>
</ul>
<hr />
<h3>2. Use JOIN FETCH</h3>
<p>If you actually need the related entities, <em>JOIN FETCH</em> is another common solution.</p>
<p>For example:</p>
<pre><code class="language-java">@Query(""" 
    SELECT DISTINCT d FROM Department d JOIN FETCH d.employees """) 
List findAllDepartmentsWithEmployees();
</code></pre>
<p>Hibernate can generate a SQL join instead of separately querying employees for every department.</p>
<p>Conceptually, we’re moving from:</p>
<pre><code class="language-plaintext">Department query 
      + 
N employee queries
</code></pre>
<p>to something closer to:</p>
<pre><code class="language-plaintext">One joined query
</code></pre>
<p>For example:</p>
<pre><code class="language-sql">SELECT d.*, e.* 
FROM department d 
JOIN employee e 
    ON e.department_id = d.id;
</code></pre>
<p><strong>Why <em>DISTINCT</em>?</strong></p>
<p>A SQL join can produce multiple rows for the same department if that department has multiple employees.</p>
<p>For example:</p>
<pre><code class="language-plaintext">Department 1 → Employee A 
Department 1 → Employee B 
Department 1 → Employee C
</code></pre>
<p>Hibernate therefore needs to make sure we don’t end up with duplicate Department objects in the result.</p>
<p>That’s why you’ll often see:</p>
<pre><code class="language-java">SELECT DISTINCT d
</code></pre>
<p>with collection fetch joins.</p>
<hr />
<h3>3. Use @EntityGraph</h3>
<p>If you don’t want to write custom JPQL, @EntityGraph provides a declarative way of specifying which relationships should be fetched.</p>
<p>For example:</p>
<pre><code class="language-java">@EntityGraph(attributePaths = {"employees"}) 
List findAll();
</code></pre>
<p>Now you’re telling Hibernate:</p>
<blockquote>
<p>“When executing this query, fetch the employees along with the departments.”</p>
</blockquote>
<p>This can make repository code easier to read, especially when the query itself doesn’t need complicated filtering or joins.</p>
<p>I particularly like <em>@EntityGraph</em> when the repository query is otherwise simple.</p>
<hr />
<h3>4. Use Batch Fetching</h3>
<p>Sometimes fetching everything in one giant join isn’t the best option.</p>
<p>Hibernate also supports <strong>batch fetching</strong>.</p>
<p>For example:</p>
<pre><code class="language-sql">@BatchSize(size = 20) 
@OneToMany(mappedBy = "department") 
private List employees;
</code></pre>
<p>Instead of doing:</p>
<pre><code class="language-sql">SELECT * FROM employee WHERE department_id = 1; 
SELECT * FROM employee WHERE department_id = 2; 
SELECT * FROM employee WHERE department_id = 3;
</code></pre>
<p>Hibernate can group IDs together and execute queries more like:</p>
<pre><code class="language-sql">SELECT * 
FROM employee 
WHERE department_id IN (1, 2, 3, ..., 20);
</code></pre>
<p>So instead of N individual queries, Hibernate can reduce the number of database round trips significantly.</p>
<p>You can also configure a default batch size globally using:</p>
<pre><code class="language-plaintext">spring.jpa.properties.hibernate.default_batch_fetch_size=20
</code></pre>
<p>This doesn’t necessarily turn N+1 into exactly one query, but it can dramatically reduce the number of queries.</p>
<hr />
<h3>5. What About <em>FetchType.EAGER</em>?</h3>
<p>This is one of the first solutions people usually think of:</p>
<pre><code class="language-plaintext">@OneToMany( 
    mappedBy = "department", 
    fetch = FetchType.EAGER 
) 
private List employees;
</code></pre>
<p>It sounds reasonable:</p>
<blockquote>
<p>“If lazy loading causes N+1, let’s just make it eager.”</p>
</blockquote>
<p>But this is generally <strong>not a good global solution</strong>.</p>
<p>The problem is that eager loading means the relationship is expected to be available whenever the entity is loaded.</p>
<p>That can lead to:</p>
<ul>
<li><p>Unnecessary data being loaded</p>
</li>
<li><p>Larger SQL queries</p>
</li>
<li><p>More memory consumption</p>
</li>
<li><p>Unexpected performance problems</p>
</li>
<li><p>Difficult-to-control entity graphs</p>
</li>
</ul>
<p>And importantly, changing a relationship to EAGER does <strong>not guarantee that every query will magically become a single efficient SQL query</strong>.</p>
<p>The better approach is usually to decide per use case what data should be fetched.</p>
<hr />
<h2>A Practical Way to Think About It</h2>
<p>When working with JPA/Hibernate, I find this mental model useful:</p>
<p>Don’t ask:</p>
<blockquote>
<p>“Should this relationship be LAZY or EAGER?”</p>
</blockquote>
<p>Instead, ask:</p>
<blockquote>
<p>“What data does this particular use case need?”</p>
</blockquote>
<p>For example:</p>
<p><strong>Use case 1 — Department list</strong></p>
<p>The UI only needs:</p>
<p>Department ID Department Name</p>
<p>Use a DTO projection.</p>
<p><strong>Use case 2 — Department details</strong></p>
<p>The UI needs:</p>
<pre><code class="language-plaintext">Department 
+ 
Employees
</code></pre>
<p>A fetch join or entity graph may make sense.</p>
<p><strong>Use case 3 — Large number of departments</strong></p>
<p>Fetching everything through one massive join may not be ideal.</p>
<p>Batch fetching might be more appropriate.</p>
<p>The solution should depend on the query and the amount of data involved.</p>
<hr />
<h2>How Do You Actually Detect N+1?</h2>
<p>One of the easiest ways is simply to look at the SQL Hibernate is generating.</p>
<p>For example, if your logs contain something like:</p>
<pre><code class="language-plaintext">select ... from department 
select ... from employee where department_id=? 
select ... from employee where department_id=? 
select ... from employee where department_id=? 
select ... from employee where department_id=?
</code></pre>
<p>and that second query keeps repeating, that’s a strong indication that you may have an N+1 problem.</p>
<p>In a real application, database monitoring and SQL logging can help identify these patterns before they become serious production issues.</p>
<hr />
<h2>Final Thoughts</h2>
<p>The N+1 query problem is one of those issues that makes you appreciate both the power and the trade-offs of ORM frameworks.</p>
<p>Hibernate saves us from writing a huge amount of boilerplate database code, but that abstraction doesn’t mean we can completely ignore what is happening at the SQL level.</p>
<p>The key lesson for me is:</p>
<blockquote>
<p>If you’re using an ORM, you should still understand the SQL it generates.</p>
</blockquote>
<p>When you notice an N+1 problem, don’t immediately switch everything to EAGER.</p>
<p>Instead, look at what the API actually needs and choose the appropriate strategy:</p>
<ul>
<li><p>D<strong>TO projections</strong> → when you only need specific fields</p>
</li>
<li><p>JOIN FETCH → when you need related entities in the same query</p>
</li>
<li><p>@EntityGraph → when you want declarative fetch planning</p>
</li>
<li><p><strong>Batch fetching</strong> → when loading relationships in groups makes sense</p>
</li>
<li><p>EAGER → use cautiously rather than as a blanket fix</p>
</li>
</ul>
<p>Once you start thinking about the database queries behind your JPA code, Hibernate becomes much less of a “black box” and much easier to optimize.</p>
]]></content:encoded></item></channel></rss>