<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Onkar Mali</title>
    <description>The latest articles on DEV Community by Onkar Mali (@onkar_mali_d89024ff6d2701).</description>
    <link>https://dev.to/onkar_mali_d89024ff6d2701</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3729994%2Fc392f5c5-77e1-4568-b3e2-095a522412b8.jpg</url>
      <title>DEV Community: Onkar Mali</title>
      <link>https://dev.to/onkar_mali_d89024ff6d2701</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/onkar_mali_d89024ff6d2701"/>
    <language>en</language>
    <item>
      <title>Lazy vs Eager Loading in Java</title>
      <dc:creator>Onkar Mali</dc:creator>
      <pubDate>Sat, 24 Jan 2026 10:44:31 +0000</pubDate>
      <link>https://dev.to/onkar_mali_d89024ff6d2701/lazy-vs-eager-loading-in-java-4jc8</link>
      <guid>https://dev.to/onkar_mali_d89024ff6d2701/lazy-vs-eager-loading-in-java-4jc8</guid>
      <description>&lt;p&gt;The one decision that silently decides whether your application feels instant or painfully slow&lt;/p&gt;

&lt;p&gt;Your API is slow. Not because Java is slow. Not because your database is weak.&lt;br&gt;
Because of one invisible fetch you did not think about.&lt;br&gt;
Somewhere deep inside your entity mapping, data is loading when it should not. Or worse, not loading when you desperately need it.&lt;br&gt;
You ship the feature. Traffic grows. Memory spikes. Queries explode. Production burns quietly.&lt;br&gt;
This article exists to stop that from happening to you.&lt;br&gt;
If you write Java. If you use JPA or Hibernate. If performance, scale, or clean architecture matters to you.&lt;br&gt;
Read this carefully.&lt;/p&gt;

&lt;p&gt;Why Lazy vs Eager Loading Is Not a “Beginner Topic”&lt;br&gt;
Most developers learn it like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lazy loading means data loads later&lt;/li&gt;
&lt;li&gt;Eager loading means data loads immediately
That explanation is technically correct. It is also dangerously incomplete.
Because Lazy vs Eager is not about syntax. It is about control.
And loss of control is what kills real-world systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Core Difference (Without the Noise)&lt;br&gt;
Lazy Loading&lt;br&gt;
Data loads only when accessed&lt;br&gt;
Eager Loading&lt;br&gt;
Data loads immediately with the parent&lt;br&gt;
Simple definition. Massive consequences.&lt;br&gt;
Let us make this concrete.&lt;/p&gt;

&lt;p&gt;A Realistic Domain Example&lt;br&gt;
Assume a simple system.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A User&lt;/li&gt;
&lt;li&gt;Each user has many Orders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/entity"&gt;@entity&lt;/a&gt;&lt;br&gt;
class User {&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/id"&gt;@id&lt;/a&gt;&lt;br&gt;
private Long id;&lt;/p&gt;

&lt;p&gt;private String name;&lt;/p&gt;

&lt;p&gt;@OneToMany(mappedBy = “user”, fetch = FetchType.LAZY)&lt;br&gt;
private List orders;&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/entity"&gt;@entity&lt;/a&gt;&lt;br&gt;
class Order {&lt;/p&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/id"&gt;@id&lt;/a&gt;&lt;br&gt;
private Long id;&lt;/p&gt;

&lt;p&gt;private double total;&lt;/p&gt;

&lt;p&gt;@ManyToOne&lt;br&gt;
private User user;&lt;br&gt;
}&lt;br&gt;
Nothing fancy. Nothing suspicious.&lt;br&gt;
And yet this is where most performance problems begin.&lt;/p&gt;

&lt;p&gt;What Lazy Loading Actually Does at Runtime&lt;br&gt;
You fetch a user.&lt;/p&gt;

&lt;p&gt;User user = em.find(User.class, 1L);&lt;br&gt;
Hibernate runs:&lt;/p&gt;

&lt;p&gt;SELECT * FROM user WHERE id = 1;&lt;br&gt;
So far, so good.&lt;br&gt;
Now this line appears harmless:&lt;/p&gt;

&lt;p&gt;user.getOrders().size();&lt;br&gt;
But behind the scenes:&lt;/p&gt;

&lt;p&gt;SELECT * FROM orders WHERE user_id = 1;&lt;br&gt;
A second query just fired.&lt;br&gt;
Not because you asked for orders explicitly. Because you touched the collection.&lt;br&gt;
Now imagine this inside a loop.&lt;/p&gt;

&lt;p&gt;The Famous N+1 Query Disaster&lt;/p&gt;

&lt;p&gt;List users = em.createQuery(“from User”, User.class).getResultList();&lt;/p&gt;

&lt;p&gt;for (User u : users) {&lt;br&gt;
System.out.println(u.getOrders().size());&lt;br&gt;
}&lt;br&gt;
What you think happens&lt;br&gt;
One query for users. One query for orders.&lt;br&gt;
What actually happens&lt;/p&gt;

&lt;p&gt;SELECT * FROM user;&lt;br&gt;
SELECT * FROM orders WHERE user_id = 1;&lt;br&gt;
SELECT * FROM orders WHERE user_id = 2;&lt;br&gt;
SELECT * FROM orders WHERE user_id = 3;&lt;br&gt;
…&lt;br&gt;
One query becomes hundreds.&lt;br&gt;
This is not a theoretical issue. This is one of the most common production bottlenecks in Java applications.&lt;/p&gt;

&lt;p&gt;Benchmarks: The Cost of Ignoring Fetch Strategy&lt;br&gt;
Scenario&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1,000 users&lt;/li&gt;
&lt;li&gt;Each user has 10 orders
Case 1: Lazy loading inside a loop
Metric Result
SQL queries 1,001
Response time ~850 ms
Database CPU High
Memory usage Moderate
Case 2: Controlled eager fetch using JOIN FETCH&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;List users = em.createQuery(&lt;br&gt;
“select u from User u join fetch u.orders”,&lt;br&gt;
User.class&lt;br&gt;
).getResultList();&lt;br&gt;
Metric Result&lt;br&gt;
SQL queries 1&lt;br&gt;
Response time ~90 ms&lt;br&gt;
Database CPU Low&lt;br&gt;
Memory usage Higher but predictable&lt;br&gt;
Same data. Ten times faster.&lt;/p&gt;

&lt;p&gt;Eager Loading Is Not the Villain Either&lt;br&gt;
Many developers react by switching everything to eager.&lt;/p&gt;

&lt;p&gt;@OneToMany(fetch = FetchType.EAGER)&lt;br&gt;
private List orders;&lt;br&gt;
This feels safe. It is not.&lt;br&gt;
Because eager loading has no brakes.&lt;/p&gt;

&lt;p&gt;What Eager Loading Actually Does&lt;br&gt;
Every time you fetch User, Hibernate must fetch orders, even if you do not need them.&lt;br&gt;
Login screen Admin dashboard Audit job Background scheduler&lt;br&gt;
All of them now load orders.&lt;br&gt;
Even when you never touch them.&lt;/p&gt;

&lt;p&gt;Architecture View: Lazy vs Eager&lt;br&gt;
Lazy Loading&lt;/p&gt;

&lt;p&gt;[Service]&lt;br&gt;
|&lt;br&gt;
| — → User&lt;br&gt;
|&lt;br&gt;
| — → Orders (loaded only when touched)&lt;br&gt;
Eager Loading&lt;/p&gt;

&lt;p&gt;[Service]&lt;br&gt;
|&lt;br&gt;
| — → User&lt;br&gt;
|&lt;br&gt;
| — → Orders (always loaded)&lt;br&gt;
Lazy defers cost. Eager commits upfront.&lt;br&gt;
The mistake is choosing one globally.&lt;/p&gt;

&lt;p&gt;The Right Mental Model&lt;br&gt;
Stop asking:&lt;br&gt;
Should I use Lazy or Eager?&lt;br&gt;
Start asking:&lt;br&gt;
When do I want this data, and who controls that decision?&lt;/p&gt;

&lt;p&gt;The Professional Pattern: Lazy by Default, Explicit Fetching&lt;br&gt;
Rule 1&lt;br&gt;
Use LAZY on associations.&lt;br&gt;
Rule 2&lt;br&gt;
Fetch eagerly only in queries, not mappings.&lt;br&gt;
Example:&lt;/p&gt;

&lt;p&gt;@OneToMany(mappedBy = “user”, fetch = FetchType.LAZY)&lt;br&gt;
private List orders;&lt;br&gt;
Then control fetching here:&lt;/p&gt;

&lt;p&gt;select u from User u join fetch u.orders where u.id = :id&lt;br&gt;
This keeps your entities clean. Your performance predictable. Your intent explicit.&lt;/p&gt;

&lt;p&gt;Avoiding LazyInitializationException Without Breaking Design&lt;br&gt;
The exception appears when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Session is closed&lt;/li&gt;
&lt;li&gt;Lazy field accessed
Bad fix:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FetchType.EAGER&lt;br&gt;
Good fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetch what you need inside the service&lt;/li&gt;
&lt;li&gt;Map to DTOs&lt;/li&gt;
&lt;li&gt;Close session confidently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DTO Projection: Clean and Fast&lt;/p&gt;

&lt;p&gt;select new com.app.UserSummary(u.id, u.name, count(o))&lt;br&gt;
from User u&lt;br&gt;
left join u.orders o&lt;br&gt;
group by u.id, u.name&lt;br&gt;
Now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No lazy issues&lt;/li&gt;
&lt;li&gt;No heavy entities&lt;/li&gt;
&lt;li&gt;No wasted memory
This is how high-scale systems stay calm under load.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Warning Signs You Are Using Fetching Wrong&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You see N+1 queries in logs&lt;/li&gt;
&lt;li&gt;You mark everything EAGER out of fear&lt;/li&gt;
&lt;li&gt;You rely on Open Session in View&lt;/li&gt;
&lt;li&gt;You debug LazyInitializationException in production
If any of these sound familiar, this article was for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Final Advice From One Developer to Another&lt;br&gt;
Lazy loading is not slow. Eager loading is not fast.&lt;br&gt;
Uncontrolled loading is the real enemy.&lt;br&gt;
When you control when and why data loads, Java feels sharp, fast, and elegant.&lt;br&gt;
When you do not, no hardware upgrade will save you.&lt;br&gt;
Choose intentionally. Fetch explicitly. Design like someone who expects their system to grow.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>database</category>
      <category>java</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
