The EntityManager methods find() and getReference() both look up an entity by its primary key, but find() loads the row right away, whereas getReference() returns a placeholder object without running any SQL. The method find() either returns the real entity or null when the row does not exist. The method getReference() returns a proxy, a generated subclass of the entity that knows only the id and loads the rest on first use.
We use find() when we read or change the entity’s data, and getReference() when we need an entity only to set a foreign key, for example to assign a support ticket to an agent whose id we already know.
The following example maps a ticket that refers to its agent by foreign key, and calls both methods with the SQL of each line as a comment.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "assignee_id")
private Agent assignee;
Agent agent = em.find(Agent.class, 1L); // select a1_0.id,a1_0.name from Agent a1_0 where a1_0.id=?
String name = agent.getName(); // "Lokesh", no SQL
Agent none = em.find(Agent.class, 99L); // none = null
Agent ref = em.getReference(Agent.class, 1L); // no SQL, ref is a proxy
Long id = ref.getId(); // 1, no SQL
String refName = ref.getName(); // select a1_0.id,a1_0.name from Agent a1_0 where a1_0.id=?
ticket.setAssignee(em.getReference(Agent.class, 1L));
em.persist(ticket); // insert into Ticket (assignee_id,subject,id) values (?,?,default)
String missing = em.getReference(Agent.class, 99L).getName(); // EntityNotFoundException
Notice that the ticket insert with getReference() needs no SELECT for the agent, and that a missing id shows up either as null from find() or as an EntityNotFoundException from the proxy.
Next, we see how each method loads the entity and walk through the support desk example, including the errors a proxy can throw. The FAQs cover Spring Data JPA’s getReferenceById() and the replacements for Session.load() and Session.get().
1. How find() and getReference() Load an Entity
Both methods first look in the persistence context, the set of entities the current EntityManager already holds. When the entity is not there, the two methods behave differently.
- The method find() runs a SELECT at the call and either returns a fully loaded Agent or null.
- The method getReference() runs no SQL. It returns an Agent$HibernateProxy that holds only the id, and runs the SELECT when we first call a getter other than getId().
A timeline of the SQL that each call sends makes the difference visible.

| find() | getReference() | |
|---|---|---|
| SQL at the call | One SELECT (none if already loaded) | None |
| Returned object | The entity itself | A proxy subclass of the entity |
| Row does not exist | Returns null | Returns a proxy; EntityNotFoundException on first access |
| Used after the EntityManager closes | Works, the data is loaded | LazyInitializationException, unless loaded before |
| Best for | Reading or changing the entity | Setting a foreign key, deleting by id |
2. find() and getReference() Example
The following example is a small support desk built with Hibernate 7.4.11, Java 25 and an in-memory H2 database. The comments show the SQL that Hibernate prints for each call.
2.1. Support Desk Model
An agent has an id and a name, and a ticket has a subject and an optional assignee, mapped as a lazy @ManyToOne. The Ticket table stores only the agent’s id in its assignee_id column.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String subject;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "assignee_id")
private Agent assignee;
The data is one agent, “Lokesh” with id 1, and one ticket, “Printer is offline”, with no assignee yet.
2.2. Loading an Entity With find()
The method find() sends the SELECT at the call and returns the entity class itself. A second find() for the same id in the same EntityManager returns the same object from the persistence context, so it runs no SQL.
Agent agent = em.find(Agent.class, 1L); // select a1_0.id,a1_0.name from Agent a1_0 where a1_0.id=?
Class<?> type = agent.getClass(); // class Agent
String name = agent.getName(); // "Lokesh"
Agent again = em.find(Agent.class, 1L); // no SQL, again == agent
When the row does not exist, find() returns null and throws nothing. It still runs the SELECT to find that out.
Agent agent = em.find(Agent.class, 99L); // select a1_0.id,a1_0.name from Agent a1_0 where a1_0.id=?
// agent = null
2.3. Getting a Proxy With getReference()
The method getReference() returns an instance of a class that Hibernate generates at runtime. Because the generated class extends Agent, the variable type and instanceof checks work as usual. The proxy has its id set and loads everything else on demand.
Agent agent = em.getReference(Agent.class, 1L); // no SQL
String className = agent.getClass().getName(); // com.howtodoinjava.hibernate.reference.Agent$HibernateProxy
boolean isAgent = agent instanceof Agent; // true
Long id = agent.getId(); // 1, no SQL
boolean before = Hibernate.isInitialized(agent); // false
String name = agent.getName(); // select a1_0.id,a1_0.name from Agent a1_0 where a1_0.id=?
// "Lokesh"
boolean after = Hibernate.isInitialized(agent); // true
The proxy decides what to do on each getter call. It answers getId() itself, whereas any other getter loads the row once, and from then on the proxy forwards every call to the loaded Agent.

2.4. Assigning a Ticket Without Loading the Agent
Setting a foreign key is the main reason getReference() exists. To save the ticket, Hibernate needs only the agent’s id for the assignee_id column, so loading the agent’s name and other columns is wasted work.
emf.runInTransaction(em -> {
Ticket ticket = new Ticket("VPN keeps disconnecting");
ticket.setAssignee(em.getReference(Agent.class, agentId));
em.persist(ticket);
});
// insert into Ticket (assignee_id,subject,id) values (?,?,default)
The same code with find() sends an extra SELECT for an agent we never read.
ticket.setAssignee(em.find(Agent.class, agentId));
em.persist(ticket);
// select a1_0.id,a1_0.name from Agent a1_0 where a1_0.id=?
// insert into Ticket (assignee_id,subject,id) values (?,?,default)
Reassigning an existing ticket works the same way. We load the ticket because we change it, and only reference the agent.
Ticket ticket = em.find(Ticket.class, ticketId); // select t1_0.id,t1_0.assignee_id,t1_0.subject from Ticket t1_0 where t1_0.id=?
ticket.setAssignee(em.getReference(Agent.class, agentId));
// at commit: update Ticket set assignee_id=?,subject=? where id=?
One saved SELECT looks small, but it is saved for every ticket, which adds up in an import or a batch insert of thousands of rows. For example, a nightly job that imports 10,000 tickets from an email inbox and assigns each one to an agent sends 10,000 fewer queries with getReference().
2.5. When getReference() Throws EntityNotFoundException
The method getReference() does not check that the row exists. The EntityNotFoundException comes later, at the first getter call that needs the data, not at the getReference() call.
Agent agent = em.getReference(Agent.class, 99L); // no SQL, no error
Long id = agent.getId(); // 99
String name = agent.getName(); // select a1_0.id,a1_0.name from Agent a1_0 where a1_0.id=?, then EntityNotFoundException
jakarta.persistence.EntityNotFoundException: No row with the given identifier exists for entity [com.howtodoinjava.hibernate.reference.Agent with id '99']
Jakarta Persistence allows a provider to throw the exception already in getReference(), but Hibernate does not. When we never read the proxy and only use it as a foreign key, there is no EntityNotFoundException at all. The database rejects the INSERT instead.
ticket.setAssignee(em.getReference(Agent.class, 99L));
em.persist(ticket); // insert into Ticket (assignee_id,subject,id) values (?,?,default)
org.hibernate.exception.ConstraintViolationException: could not execute statement [Referential integrity constraint violation: "FKSPV3UFG8AKGDRCOTXC1DRQKUM: PUBLIC.TICKET FOREIGN KEY(ASSIGNEE_ID) REFERENCES PUBLIC.AGENT(ID) (CAST(99 AS BIGINT))"; ...]
Hibernate generates our ids with IDENTITY, so it runs the INSERT inside persist(). With a sequence, the INSERT and the error come at flush, which by default happens at commit. When the id comes from user input, we either check it with find() first or handle the constraint error, because a foreign key on the table is the only check getReference() gets.
| Missing row, what we do | find() | getReference() |
|---|---|---|
| Call the method | SELECT, returns null | No SQL, returns a proxy |
| Read getId() | NullPointerException on null | Returns 99 |
| Read getName() | NullPointerException on null | SELECT, then EntityNotFoundException |
| Use it as a foreign key | Assigns null (no error) | Foreign key violation on INSERT |
2.6. LazyInitializationException After the EntityManager Closes
A proxy loads its data through the EntityManager that created it. Once that EntityManager is closed, the proxy has no way to run the SELECT.
Agent agent = emf.callInTransaction(em -> em.getReference(Agent.class, agentId));
Long id = agent.getId(); // 1, still works
String name = agent.getName(); // LazyInitializationException
org.hibernate.LazyInitializationException: Could not initialize proxy [com.howtodoinjava.hibernate.reference.Agent#1] - no session
The proxy throws the same LazyInitializationException as lazy associations do. We fix it in one of two ways.
- Use find() when the caller needs the data after the transaction.
- Load the proxy while the EntityManager is open, with Hibernate.initialize(agent) or any getter call.
Agent agent = emf.callInTransaction(em -> {
Agent ref = em.getReference(Agent.class, agentId);
Hibernate.initialize(ref); // select a1_0.id,a1_0.name from Agent a1_0 where a1_0.id=?
return ref;
});
String name = agent.getName(); // "Lokesh"
2.7. New find() and getReference() Options in JPA 3.2
Jakarta Persistence 3.2, which Hibernate 7 implements, adds three overloads. With it, the method find() accepts FindOption values such as LockModeType, Timeout, CacheRetrieveMode and CacheStoreMode, in any combination.
Agent agent = em.find(Agent.class, agentId, LockModeType.PESSIMISTIC_WRITE, Timeout.seconds(2));
// select a1_0.id,a1_0.name from Agent a1_0 where a1_0.id=? for update wait 2
The method find() also accepts an EntityGraph, a list of associations to load together with the entity. With the graph, the ticket and its assignee come back in one query instead of a ticket with an agent proxy.
EntityGraph<Ticket> graph = em.createEntityGraph(Ticket.class);
graph.addAttributeNode("assignee");
Ticket ticket = em.find(graph, ticketId);
// select t1_0.id,a1_0.id,a1_0.name,t1_0.subject from Ticket t1_0
// left join Agent a1_0 on a1_0.id=t1_0.assignee_id where t1_0.id=?
boolean loaded = Hibernate.isInitialized(ticket.getAssignee()); // true
The method getReference() gets an overload that takes an entity instead of a class and an id. The new overload helps with a detached entity, which is an entity loaded by an EntityManager that is already closed.
Agent loaded = emf.callInTransaction(em -> em.find(Agent.class, agentId)); // detached after this line
emf.runInTransaction(em -> {
Ticket ticket = new Ticket("Monitor is flickering");
ticket.setAssignee(em.getReference(loaded)); // no SQL
em.persist(ticket); // insert into Ticket (assignee_id,subject,id) values (?,?,default)
});
The complete project on GitHub runs every case from section 2 and prints the SQL (mvn -q compile exec:java, mvn test). Its 23 JUnit tests count statements with Hibernate’s Statistics API.
3. getReference() vs find() FAQs
3.1. When Should I Use getReference() Instead of find()?
We use getReference() when the code needs only the id, and find() when it reads the entity’s data.
| Task | Method |
|---|---|
| Show or return the agent’s data | find() |
| Check whether an id exists | find() and compare with null |
| Change the agent’s fields | find() (a proxy would load anyway) |
| Set a foreign key, as in ticket.setAssignee() | getReference() |
| Delete by id | getReference() (see FAQ 3.4) |
| Pass the entity out of the transaction | find() |
3.2. What Is getReferenceById() in Spring Data JPA?
The method getReferenceById() is the Spring Data JPA repository method that calls EntityManager.getReference(), so it has the same proxy behavior. The method findById() calls find() and wraps the result in an Optional.
public interface AgentRepository extends JpaRepository<Agent, Long> { }
Optional<Agent> agent = agentRepository.findById(1L); // select, Optional.empty() when missing
Agent ref = agentRepository.getReferenceById(1L); // proxy, no SQL
ticket.setAssignee(ref);
Spring Data JPA 2.7 added getReferenceById(). It replaces getOne() and getById(), which are deprecated and do the same thing under older names.
3.3. Does find() Return a Proxy?
Yes, in one case, namely when the same EntityManager already handed out a proxy for that id. Hibernate keeps one object per id in the persistence context, so the call order decides the class we get back.
Agent found = em.find(Agent.class, 1L); // select ...
Agent ref = em.getReference(Agent.class, 1L); // no SQL, ref == found, class Agent
Agent ref2 = em.getReference(Agent.class, 1L); // in a new EntityManager: no SQL, a proxy
Agent found2 = em.find(Agent.class, 1L); // select ..., found2 == ref2, still the proxy
boolean initialized = Hibernate.isInitialized(found2); // true
For that reason, we never compare entity classes with getClass(). We use instanceof, or Hibernate.getClass(entity), which returns Agent. Keep in mind that Hibernate.getClass() loads an uninitialized proxy to find its real class.
3.4. Can I Update or Delete an Entity Through getReference()?
Yes. A setter on the proxy loads the row first, because Hibernate needs the old values to detect the change. A delete in Hibernate 7.4 needs no data at all.
em.getReference(Agent.class, 1L).setName("Lokesh Gupta");
// select a1_0.id,a1_0.name from Agent a1_0 where a1_0.id=?
// update Agent set name=? where id=?
em.remove(em.getReference(Agent.class, alexId));
// delete from Agent where id=?
Removing a missing id through a proxy fails at commit with Unexpected row count (expected row count 1 but was 0), because the DELETE matched no row. Hibernate also offers other ways of deleting entities, such as a bulk DELETE query.
3.5. What Replaced Session.load() and Session.get() in Hibernate 7?
The Hibernate Session had its own pair of methods, get() and load(), and Hibernate 7.4 changes both.
- The method Session.load(Class, id) no longer exists. Use getReference().
- The method Session.get(Class, id) is deprecated. Use find().
Both replacements exist on EntityManager and on Session, since Session extends EntityManager.
4. Conclusion
The method find() runs a SELECT at once and either returns the entity or null. The method getReference() returns a proxy without SQL and loads it on the first non-id getter. That getter either throws EntityNotFoundException if the row is missing, or LazyInitializationException if the EntityManager is closed. We use find() to read data, and getReference() to set foreign keys or delete by id without loading the entity.
5. References
- EntityManager JavaDoc (Jakarta Persistence 3.2)
- FindOption JavaDoc (Jakarta Persistence 3.2)
- Hibernate ORM 7.4 User Guide: Obtain an entity reference without initializing its data
- Hibernate ORM 7.4 User Guide: Obtain an entity with its data initialized
- Spring Data JPA JpaRepository JavaDoc
Happy Learning !!
hi i recently tried Hibernate 4 annotation example, where i m getting communication link failure exception.
i googled it but i didn’t get proper answer.
i m using mysql connector 3.0.8. and my database is not in idle mode.
can u clarify ?
Can you please copy your exception stacktrace here.
Hi, i removed mysql connector 3.0.8 jar and added latest version of mysql connector ver 5.0.8.. it solved my problem….
Thank you for response sir…