For a long time, one of our APIs was performing exceptionally well. Every request completed in approximately 200 milliseconds, and the application handled traffic smoothly without any noticeable issues.
Then, almost unexpectedly, the API became dramatically slower.
Response times increased from around 200 milliseconds to nearly 5 seconds. Users started experiencing delays, and overall application performance suffered significantly.
What made the situation even more confusing was that nothing major had changed.
There were no recent feature releases, no infrastructure modifications, no database migrations, and no spikes in server utilization. On the surface, everything appeared normal.
GET YOUR FREE CONSULTATION FOR YOUR BUSINESS TODAY!
The Investigation Begins
Like most developers facing a performance issue, my first assumption was that the database was responsible.
I started by checking the usual suspects:
Database performance metrics
Query execution times
CPU utilization
Memory consumption
Network latency
Application server health
Everything looked healthy.
The database was responding quickly, server resources were well within limits, and there were no obvious bottlenecks.
Yet the API remained painfully slow.
At this point, I realized the problem was likely hidden somewhere deeper within the application itself.
Understanding Hibernate's Hidden Cost
To investigate further, I enabled Hibernate SQL logging and started reviewing the generated queries.
What I discovered was surprising.
A single API request was generating hundreds of SQL queries.
The endpoint should have executed only one or two queries. Instead, Hibernate was repeatedly issuing additional queries for related entities.
The root cause was the classic N+1 Query Problem.
What is the N+1 Query Problem?
Consider a simple example involving Users and Orders.
Why a Fast API Suddenly Became 25x Slower: Debugging a Hidden Hibernate N+1 Query Problem?
For a long time, one of our APIs was performing exceptionally well. Every request completed in approximately 200 milliseconds, and the application handled traffic smoothly without any noticeable issues.
Then, almost unexpectedly, the API became dramatically slower.
Response times increased from around 200 milliseconds to nearly 5 seconds. Users started experiencing delays, and overall application performance suffered significantly.
What made the situation even more confusing was that nothing major had changed.
There were no recent feature releases, no infrastructure modifications, no database migrations, and no spikes in server utilization. On the surface, everything appeared normal.
GET YOUR FREE CONSULTATION FOR YOUR BUSINESS TODAY!
The Investigation Begins
Like most developers facing a performance issue, my first assumption was that the database was responsible.
I started by checking the usual suspects:
Everything looked healthy.
The database was responding quickly, server resources were well within limits, and there were no obvious bottlenecks.
Yet the API remained painfully slow.
At this point, I realized the problem was likely hidden somewhere deeper within the application itself.
Understanding Hibernate's Hidden Cost
To investigate further, I enabled Hibernate SQL logging and started reviewing the generated queries.
What I discovered was surprising.
A single API request was generating hundreds of SQL queries.
The endpoint should have executed only one or two queries. Instead, Hibernate was repeatedly issuing additional queries for related entities.
The root cause was the classic N+1 Query Problem.
What is the N+1 Query Problem?
Consider a simple example involving Users and Orders.
@Entity
public class User {
@OneToMany(mappedBy = “user”, fetch = FetchType.LAZY)
private List<Order> orders;
}
Now imagine retrieving all users:
List<User> users = userRepository.findAll();
Later, the application accesses the orders for each user:
for (User user : users) {
System.out.println(user.getOrders().size());
}
Query 1
SELECT * FROM users;
Then for every user:
Query 2
SELECT * FROM orders WHERE user_id = 1;
Query 3
SELECT * FROM orders WHERE user_id = 2;
Query 4
SELECT * FROM orders WHERE user_id = 3;
And so on.
If there are 100 users, Hibernate executes:
Total: 101 queries
Instead of only one or two queries.
This is known as the N+1 Query Problem.
Why It Becomes a Performance Disaster
Every database query has overhead.
Each query requires:
A single query may execute in milliseconds.
Hundreds of unnecessary queries create cumulative delays that quickly add up.
This was exactly what happened in our case.
As the dataset grew, the hidden queries multiplied.
The API that previously responded in around 200ms gradually slowed until requests were taking nearly 5 seconds.
How We Fixed It
1. Using JOIN FETCH)
Instead of allowing Hibernate to lazily load related entities one by one, we fetched everything in a single query.
@Query(“SELECT u FROM User u JOIN FETCH u.orders”)List<User> findAllUsersWithOrders();
Hibernate now retrieves users and orders together.
This dramatically reduces the number of database round trips.
2. Using EntityGraph
Another clean solution is EntityGraph.
@EntityGraph(attributePaths = {“orders”})List<User> findAll();
EntityGraph allows us to explicitly define which relationships should be loaded eagerly for a specific operation.
Benefits:
3. Reviewing Lazy Loading
Lazy loading is not bad.
In fact, it’s often the right choice.
The problem occurs when lazy-loaded collections are accessed inside loops.
We reviewed our codebase and identified areas where lazy-loaded relationships were triggering unnecessary queries.
Fetching required data upfront eliminated many hidden database calls.
4. Enabling Batch Fetching
We also enabled Hibernate batch fetching
spring.jpa.properties.hibernate.default_batch_fetch_size=50
Batch fetching allows Hibernate to load multiple entities in groups rather than issuing separate queries for each record.
This significantly reduces database communication overhead
The Results
The improvements were immediate.
The API returned to its original performance levels, and the system once again delivered fast and consistent responses.
Key Lessons Learned
1. Clean Code Doesn't Always Mean Efficient Code
An application can look perfectly designed while still generating excessive database traffic behind the scenes.
2. Query Count Matters
Two APIs may have identical response times today but behave very differently as data volumes increase.
Monitoring query count is just as important as monitoring response time
3. ORM Frameworks Are Powerful but Not Magic
Hibernate simplifies development tremendously, but developers still need to understand how it generates SQL queries.
Ignoring what’s happening underneath can lead to serious performance issues.
4. Small Problems Become Big Problems at Scale
What seems insignificant with ten records can become a major bottleneck with thousands or millions of records.
A Simple Habit That Prevents Future Problems
Since this experience, I’ve adopted a simple rule whenever building or reviewing an API:
"How many database queries will this endpoint execute?"
That single question has helped identify performance issues long before they reach production.
GET YOUR FREE CONSULTATION FOR YOUR BUSINESS TODAY!
Final Thought
Performance issues are rarely caused by one big mistake.
More often, they come from small inefficiencies that quietly accumulate over time until they become impossible to ignore.
The next time an API slows down unexpectedly, don’t just look at response times.
Look at the number of database queries being executed.
The answer might surprise you.
🚀 Have you ever encountered an N+1 Query Problem in Hibernate, Spring Boot, or another ORM? Share your experience in the comments
Contact:
☎️ RedMinds: +91 9550283428, +91 9010302031
🔗 LinkedIn: https://lnkd.in/gTFG4UME
📧 Email: 21@redmindstech.com
🌐 Website: redmindstech.com
📘 Facebook: https://lnkd.in/g8aV4uz7
📸 Instagram: https://lnkd.in/gDUtSGA4
X Twitter: https://lnkd.in/gXBgUBgw
It’s a performance issue where one initial query triggers multiple additional queries for related data, resulting in N+1 total queries.
By enabling Hibernate SQL logging and reviewing the generated SQL queries.
It increased from around 200 ms to nearly 5 seconds.
Categories
Tags