Why I regret using Hibernate's EAGER fetching

Production post-mortem • 4 min read english

It was a few years ago. I was working at a ride-hailing company. We were rebuilding the "My Rides" history screen. The requirements were seemingly simple: show the user their past 50 rides, including the pickup and drop-off points.

The mistake

Our data model had a Ride entity and a list of Waypoints. I knew the UI always needed to map the route, so I thought I was being efficient by automating the fetch:


@Entity
public class Ride {

  @OneToMany(fetch = FetchType.EAGER)
  private List<Waypoint> waypoints;
  // ...
}

On my local machine, with a seeded database of 5 rides, it was instant. I shipped it.

The failure

Then came the evening rush hour. Thousands of users opened the app.

The database CPU spiked to 99%. Queries began queuing. The entire booking service started timing out. People couldn't book cars.

I checked the logs and saw the N+1 nightmare. When a user with 50 past rides opened their history, Hibernate didn't run 1 query. It ran 51 queries.

Multiply that by 10,000 concurrent users. We were hitting the database with 500,000 queries per second.

The fix

We hot-fixed it by replacing EAGER with a specific JPQL query using JOIN FETCH.

SELECT r
FROM Ride r
         LEFT JOIN FETCH r.waypoints
WHERE r.userId = :userId

The lesson

Convenience is the enemy of scale. Hibernate's EAGER fetching is a landmine. If you don't know exactly how many SQL statements your code generates, you aren't engineering—you're guessing.

Pietro Cascio is a Senior Software Engineer and Pluralsight Author. He writes about the intersection of Java architecture and engineering culture.

Discuss this on LinkedIn →