skip to content

What is the difference between JPA's EntityManager.find() and EntityManager.getReference() when loading an entity by primary key, and when would you deliberately choose getReference()?

level: middleimportance: should knowfreq 52%

answer

  1. find = SELECT now, null if absent
  2. getReference = proxy, id only, no SQL
  3. EntityNotFoundException on first real access
  4. perfect for setting a FK before insert
  5. getClass() returns the proxy class

basics

~20 s

find() hits the database (or cache) and returns the loaded entity or null. getReference() returns a lazy proxy with only the id set, with no query until a non-id property is touched, and throws EntityNotFoundException at that point if the row is missing.

solid answer

~50 s

`find(Order.class, 1L)` materialises the entity now: a SELECT unless it is already in the persistence context or second-level cache, and `null` if there is no such row. `getReference(Order.class, 1L)` returns a **proxy** — an instance of a generated subclass (or a bytecode-enhanced entity) that holds only the identifier. No SELECT is issued until you call a method other than the id getter; then it initialises, and if the row does not exist it throws `EntityNotFoundException`. If the persistence context is already closed by then, initialisation fails instead. The classic use is setting a foreign key without reading the target row: ```java order.setCustomer(em.getReference(Customer.class, customerId)); ``` The INSERT only needs the id, so the SELECT is pure waste. It is also handy before `remove()` when the DELETE does not need the state. Costs: existence is not checked up front, `getClass()` returns the proxy class so `instanceof`/`equals` written against the concrete class can misbehave, and returning a proxy across the API boundary is asking for trouble.

code

java · 5 lines
java
Order order = new Order();
order.setCustomer(em.getReference(Customer.class, customerId)); // no SELECT
em.persist(order);   // INSERT ... (customer_id) VALUES (?)

Customer c = em.find(Customer.class, customerId); // SELECT, or null

go deeper

for a junior

Recall the contract: find loads and can return null; getReference gives a lazy proxy with only the id and can throw later. Know the set-the-foreign-key use case.

for a middle

Explain the proxy mechanism, when initialisation is triggered, and the getClass/equals trap; be able to justify choosing one over the other for a given operation.

for a senior

Talk about measured wins in batch paths, when remove-via-getReference is defeated by cascades or callbacks, and the discipline that stops proxies escaping the context boundary.

for a principal

Frame it as a policy: where identifiers rather than entities should be the currency in your model, and whether letting proxies leak beyond the data layer is worth the queries it saves.

## What a proxy is When a mapping says an association is lazy, or you call `getReference`, Hibernate does not hand you the real object. It hands you a **proxy**: historically a runtime-generated subclass of your entity (Byte Buddy today) whose methods all route through an initialiser, with only the identifier field populated. The first call to any non-identifier accessor triggers the initialising SELECT and delegates from then on. With bytecode enhancement the same idea is implemented inside the entity class itself rather than by subclassing. `find` and `getReference` are therefore two answers to 'give me the entity with this id': **materialise it now** versus **give me something that stands for it**. ## Contract differences | | find | getReference | |---|---|---| | SQL at call time | yes (unless cached) | no | | Missing row | returns `null` | throws `EntityNotFoundException` on first access | | Returned type | the entity class | possibly a proxy subclass | | Safe to use outside the context | yes | no — initialisation fails once it is closed | The null-versus-exception difference matters for control flow: `find` lets you branch on absence, `getReference` postpones the discovery to an arbitrary later point, which is exactly why it should not be used when 'does this exist?' is part of the business rule. ## When getReference earns its place **Setting an association.** To insert an order row, the database needs `customer_id` and nothing else. `find` would run a SELECT you throw away. `getReference` gives you an object whose only populated field is the id — precisely what the INSERT will read. In a loop over many rows, that removes one query per row. **Deleting.** `em.remove(em.getReference(Order.class, id))` avoids loading the entity when the DELETE needs no state. Caveat: cascades, `@PreRemove` callbacks and collection cleanup will initialise it anyway, so the saving is real only for simple entities. **Cheap existence-free lookups in bulk jobs.** Wiring up graphs during an import, where the referenced rows are known to exist. ## Pitfalls to name in an interview 1. **Deferred existence check.** No row means an exception later, at a place that has nothing to do with the id you supplied. Some Hibernate configurations can even defer or bypass the check further. If your code must react to 'not found', use `find`. 2. **Type identity.** `proxy.getClass()` is `Customer$HibernateProxy$xyz`, not `Customer`. `equals`/`hashCode` implemented with `getClass() != o.getClass()` break against proxies — use `instanceof` (or Hibernate's `Hibernate.getClass`) and compare business keys. Downcasting to a subclass fails for the same reason, which is why proxies interact badly with inheritance hierarchies. 3. **Escaping the context.** A proxy handed to a serialiser or a view after the persistence context closed cannot initialise. Call `Hibernate.initialize(proxy)`, or `Hibernate.unproxy`, or simply use `find`, before it leaves. 4. **Field access on the proxy.** Reading a field directly (rather than through a getter) from inside the entity class sees an uninitialised value; this is a real trap in `toString()` implementations and in entities mapped with field access being manipulated reflectively. 5. **It is not a cache bypass.** If the entity is already in the persistence context, `getReference` returns that instance, fully initialised — there is no proxy and no laziness. ## The mental model to state `find` = 'load it'. `getReference` = 'I only need something that carries this id'. Choose `getReference` when the identifier is genuinely all the operation needs and the row's existence is guaranteed by a foreign key anyway — the database will reject a bad `customer_id` regardless. Choose `find` when the code, the caller or the API contract needs to know whether the entity exists, or when the object will be read, rendered or serialised.

  • What happens if the id passed to getReference() does not exist in the database?
    Nothing at call time — you get a proxy. The failure surfaces on first access to a non-identifier property, as EntityNotFoundException, potentially far from the original call. If you also persist a row referencing it, the database's foreign key constraint may reject the insert first.
  • Why can equals()/hashCode() written with getClass() comparisons break on proxies?
    A proxy is an instance of a generated subclass, so getClass() returns that subclass, not the entity class, and a strict class equality check fails against a real instance of the same row. Use instanceof (or Hibernate.getProxyClass/getClass helpers) and compare a stable business key or the identifier instead.

find fetches the file from the archive. getReference hands you the file's reference number — enough to write it on a form, useless if you actually need to read the contents and the archive has closed.

saying these in an interview costs you the question

  • Saying getReference returns null when the row is missing
  • Believing getReference never issues SQL at all, even after you read a field
  • Handing a proxy to a serialiser or view layer and blaming Hibernate when it cannot initialise
  • Assuming getReference always returns a proxy, even when the entity is already in the persistence context

context