Fetch Types: EAGER vs LAZY
Fetch Types: EAGER vs LAZY
Every association you map with Hibernate — @OneToOne, @OneToMany, @ManyToOne, or @ManyToMany — carries a fetch strategy that answers one question: when should Hibernate load the related data? The answer has profound consequences for correctness, memory use, and query performance. Getting it wrong is one of the most common causes of slow Spring Boot applications.
The Two Strategies
EAGER fetching loads the association immediately, in the same operation that loads the owning entity. If you load a Customer and the orders collection is EAGER, Hibernate will execute whatever SQL is necessary to populate that collection right away — you never hold a Customer whose orders field is uninitialized.
LAZY fetching defers the load. Hibernate gives you a proxy (for a single association) or an uninitialised collection wrapper (for a collection). The real SQL fires only the first time your code actually touches that proxy or iterates the collection.
Hibernate's Defaults
The JPA specification defines the defaults and Hibernate obeys them:
@ManyToOne— defaults to EAGER@OneToOne— defaults to EAGER@OneToMany— defaults to LAZY@ManyToMany— defaults to LAZY
You override the default with the fetch attribute:
How LAZY Works in Practice
When you mark an association LAZY, Hibernate substitutes a proxy object (for @ManyToOne / @OneToOne) or an uninitialised PersistentBag / PersistentList (for collections). The proxy looks like the real type but contains only the primary key. As soon as any non-identifier getter is called, Hibernate fires the SQL and replaces the proxy with the real data.
LazyInitializationException. This is the most frequently encountered Hibernate runtime error.
LazyInitializationException — The Classic Mistake
The error appears when you load an entity inside a service method, return it to a controller or serializer that runs outside the transaction, and then the serializer tries to traverse an uninitialized collection:
There are three correct solutions, in order of preference:
- Use a DTO — project only what the caller needs inside the transaction; never expose entities from controllers.
- Use a JOIN FETCH query — force-load exactly the associations you need for this specific use case (covered in lesson 8).
- Use
@Transactionalon the service method — keeps the session open while the data is accessed (be careful with the Open-Session-in-View anti-pattern below).
Open-Session-in-View — A Contentious Default
Spring Boot enables Open Session in View (OSIV) by default (spring.jpa.open-in-view=true). This keeps the Hibernate session open for the entire HTTP request lifecycle, including the view-rendering / JSON-serialisation phase. It masks LazyInitializationException but at a serious cost: the database connection is held for the full duration of the request, including any remote calls or slow rendering that happens after the service layer returns.
spring.jpa.open-in-view=false in application.properties. You will get LazyInitializationExceptions at first — treat them as useful signals telling you exactly which associations need a JOIN FETCH or a DTO projection. The discipline pays off in lower connection-pool pressure and more predictable latency.
Choosing the Right Strategy
The rule of thumb used by experienced Hibernate developers:
- Always use
LAZYas your default for all associations. Explicitly fetch what you need per query. EAGERat the mapping level is only appropriate for tiny, truly-always-needed value objects that will never grow (rare).- Fetch eagerly per query using
JOIN FETCHin JPQL or Entity Graphs — not by changing the mapping.
EAGER Can Hide Cartesian Products
When an entity has two or more EAGER collections, Hibernate may join them both in a single SQL query and produce a Cartesian product: if a Customer has 10 orders and each order has 5 lines, the join returns 50 rows for one customer. Hibernate deduplicates them in memory, but the database did 50× the work. This is often invisible during development (small data sets) and catastrophic in production.
Summary
Fetch type is the knob that controls when SQL is emitted for associated data. The JPA defaults — EAGER for @ManyToOne and @OneToOne, LAZY for collections — should be overridden so that everything defaults to LAZY. Load associations eagerly only when you explicitly need them for a specific operation, using per-query mechanisms like JOIN FETCH or Entity Graphs rather than changing the mapping. Disable Open-Session-in-View, treat LazyInitializationException as a diagnostic, and prefer DTO projections. The next two lessons translate these principles into concrete JPQL and Entity Graph patterns.