In JPA, what is the default fetch type for each of the four association annotations — @ManyToOne, @OneToOne, @OneToMany and @ManyToMany — and why does the default differ between them?
answer
- to-one eager, to-many lazy
- @ManyToOne + @OneToOne = EAGER by default
- @OneToMany + @ManyToMany + @ElementCollection = LAZY
- EAGER can't be undone per query; LAZY can be upgraded
- LAZY is a hint, EAGER is a requirement
basics
~20 sTo-one associations (@ManyToOne, @OneToOne) default to EAGER. To-many associations (@OneToMany, @ManyToMany) default to LAZY. The spec assumes fetching one extra row is cheap and fetching a whole collection is not. Most teams override the to-one defaults to LAZY.
solid answer
~50 sThe JPA defaults are: `@ManyToOne` EAGER, `@OneToOne` EAGER, `@OneToMany` LAZY, `@ManyToMany` LAZY. The rule of thumb is "to-one is eager, to-many is lazy" — the spec authors assumed a single associated row costs one join and is usually wanted, while a collection can be arbitrarily large. In practice the to-one defaults are widely considered a mistake and teams write `@ManyToOne(fetch = FetchType.LAZY)` everywhere. The reason is asymmetry of control: an association mapped LAZY can be made eager per query with a fetch join or an entity graph, but an association mapped EAGER cannot be made lazy for a single query — Hibernate will always materialise it, adding joins or extra selects to every query that returns that entity, including ones that never touch the association. So the mapping should express the cheapest safe default (LAZY), and the query should express what this particular use case needs.
code
java · 12 lines@Entity
class Order {
@Id Long id;
// default is EAGER - almost always overridden
@ManyToOne(fetch = FetchType.LAZY)
Customer customer;
// default is already LAZY
@OneToMany(mappedBy = "order")
Set<OrderLine> lines;
}go deeper
Know the four defaults cold and the to-one/to-many mnemonic, and be able to say what LAZY versus EAGER means for when the SQL runs.
Add what each default actually emits — a join on find(), secondary selects after a JPQL result list — and the convention of marking to-ones LAZY explicitly.
Lead with the asymmetry argument (LAZY is upgradable per query, EAGER is not) and mention the structural cases where LAZY is silently ignored.
Frame it as a policy: mappings encode the cheapest safe default, fetch plans live with the use case, and enforcement belongs in review or a static check rather than in individual annotations.
## What a fetch type is Every association mapping in JPA carries a `fetch` attribute with two possible values, `FetchType.EAGER` and `FetchType.LAZY`. It tells the persistence provider *when* the associated data must be present in memory: - **EAGER** — by the time the owning entity is handed to your code, the association must already be populated. The provider has to load it in the same operation, either by adding a join to the SQL or by running an additional SELECT. - **LAZY** — the association may be represented by a placeholder. The real data is loaded only if and when your code actually reads it, at which point the provider issues SQL to fill it in. LAZY is formally a *hint*: a provider is allowed to load eagerly anyway. EAGER is a *requirement*: the provider must honour it. ## The four defaults | Annotation | Default | |---|---| | `@ManyToOne` | `EAGER` | | `@OneToOne` | `EAGER` | | `@OneToMany` | `LAZY` | | `@ManyToMany` | `LAZY` | The mnemonic is **"to-one eager, to-many lazy"**. Note that `@ElementCollection` also defaults to LAZY, and `@Basic` fields default to EAGER. ## Why the spec chose these A to-one association points at exactly one row, reachable through a foreign key the owning row already holds. Loading it costs one join or one extra keyed select — bounded, predictable, and usually what the caller wanted (you rarely load an order without caring who placed it). A to-many association points at an unbounded set: an entity might have three children or three hundred thousand. Loading that by default would make simple reads unpredictably expensive, so the spec left collections lazy. ## What the defaults actually emit When you call `em.find(Order.class, 1L)` on an entity with an EAGER `@ManyToOne customer`, Hibernate builds a fetch plan and typically emits **one** statement with an outer join to `customer`. That looks harmless. The cost shows up with queries. A JPQL query such as `select o from Order o` is executed close to how it is written; the eager to-one is not in the select clause, so Hibernate satisfies the EAGER contract afterwards, issuing a secondary select per distinct customer that is not already in the persistence context. A 500-row page can become hundreds of statements. An EAGER collection is worse: joining it multiplies rows, and Hibernate may fall back to per-row selects. ## Why teams override the to-one defaults The decisive argument is **asymmetry of control**: - A **LAZY** mapping can be made eager *for one query* with `join fetch` or an entity graph. You pay the cost only where you need the data. - An **EAGER** mapping cannot be made lazy for one query. There is no standard "fetch = none for this query" switch. Every query returning that entity pays, forever, including reports, exports and count-style reads that never dereference the association. So the mapping should encode the *cheapest safe* default and each query should declare the data it actually needs. The common convention is: mark every `@ManyToOne` and `@OneToOne` explicitly as `fetch = FetchType.LAZY`, leave collections at their LAZY default, and never rely on the implicit value — writing it out also documents intent for the next reader. ## Caveats and edge cases - **LAZY on a to-one needs a proxyable class.** Hibernate implements it by handing you a generated subclass; a `final` entity class, or final getters, silently defeats it and you get eager loading instead. - **The inverse side of a bidirectional `@OneToOne` (the `mappedBy` side) is effectively eager** unless you use bytecode enhancement, because Hibernate cannot know whether the other row exists without querying. - **Derived identifiers and `@MapsId`** change the picture: when the child's primary key *is* the parent's key, the reference can be created without a query. - **LAZY is only a hint in the spec** — but Hibernate honours it for both to-one and to-many when it technically can. - **Making everything LAZY does not make queries fast**; it moves the cost to whoever dereferences the association, which is exactly why fetch plans belong at the query level. ## What good sounds like in an interview State the four defaults crisply, give the to-one/to-many rationale in one sentence, then volunteer the practical override and the asymmetry argument. That last part is what separates recall from judgement.
- If LAZY is only a hint in the specification, can you rely on it?With Hibernate, yes, in practice: it honours LAZY for collections and for to-one associations whenever it can build a proxy or has bytecode enhancement available. The cases where it silently ignores the hint are structural — a final entity class or final accessors, and the mappedBy side of a @OneToOne without enhancement. Those are worth knowing precisely because the failure is silent: nothing throws, you just get extra SQL.
- You have an association mapped EAGER and one report endpoint that never uses it. What are your options?There is no per-query way to demote an EAGER mapping, so the options are: change the mapping to LAZY and add an explicit fetch plan to the queries that do need the data; or stop returning the entity from that endpoint and project the columns you need directly into a DTO, which bypasses the fetch plan entirely. Changing the mapping is usually the right call, with the fetch joins added query by query.
EAGER is a standing order for the newspaper to arrive with every delivery whether or not you read it; LAZY is asking for it only on the days you actually sit down with it. You can always ask for today's paper, but you cannot un-deliver a standing order for one particular morning.
saying these in an interview costs you the question
- Saying everything defaults to LAZY — the to-one associations do not.
- Claiming EAGER always means a single joined query; for JPQL/HQL result lists it frequently becomes one extra select per row.
- Thinking fetch = LAZY on a @ManyToOne guarantees laziness even when the entity class is final.
- Treating EAGER as a performance optimisation because it 'avoids extra queries later', without noticing it is paid on every query returning the entity.
- Believing you can override an EAGER mapping to lazy for a single query.