Have you ever encountered a LazyInitializationException and spent hours debugging why your data wasn’t there? Or noticed your application’s performance dipping because of unnecessary database hits? Choosing between Hibernate get() vs load() is a fundamental decision that every Java developer faces, yet it remains one of the most misunderstood aspects of the Hibernate framework.
In this guide, we break down the mechanics of these two methods in Hibernate 7, explore the Proxy mechanism, and help you decide which tool to pull from your persistence toolbox.
The Problem: Performance vs. Reliability
In high-performance Java applications, every millisecond counts. Using the wrong fetching strategy risks:
- Unnecessary I/O: Hitting the database for data you might not even need, leading to high latency.
- Runtime Crashes: Encountering
ObjectNotFoundExceptionwhen you expected anullcheck to safely handle missing records. - Memory Bloat: Loading heavy entity graphs into the persistence context prematurely.
Classic Hibernate provided two distinct methods to retrieve data: session.get() and session.load(). Important: Hibernate 7 removed session.load() entirely — its exact replacement is the JPA-standard session.getReference(), which behaves identically. session.get() still exists and corresponds to find(). Everything this article says about load() semantics applies unchanged to getReference(); the code examples below use getReference() so they compile on Hibernate 7.
1. session.get() — The Direct Approach
The get() method is straightforward: “Go to the database right now and give me the data.”
How it Works Internally
When you call session.get(), Hibernate first checks the First Level Cache (the Session). If not found there, it checks the Second Level Cache (if enabled). Only if still not found does it fire a SQL SELECT statement.
- Eager Loading: Hits the database immediately unless the object is already in the cache.
- Null Return: Returns
nullif the identifier doesn’t exist. The “safe” choice for checking existence. - Real Object: Returns a fully initialized object with all non-lazy fields populated.
try (Session session = sessionFactory.openSession()) {
// Hibernate checks L1 cache, then L2 cache, then fires:
// SELECT * FROM users WHERE id = 1
User user = session.get(User.class, 1L);
if (user != null) {
System.out.println("User found: " + user.getName());
// Output: User found: Ankur
} else {
System.out.println("User with ID 1 does not exist.");
}
// For a non-existent ID:
User missing = session.get(User.class, 9999L);
System.out.println("Missing user: " + missing);
// Output: Missing user: null
}
2. session.getReference() (formerly load()) — The Lazy Specialist
The getReference() method (the JPA-standard replacement for the removed load()) is based on Lazy Initialization. It says: “I trust that this ID exists. Here is a placeholder (Proxy). Don’t hit the database until I actually access a property other than the ID.”
The Proxy Mechanism
A Hibernate Proxy is a runtime-generated subclass of your entity. It holds the ID you provided but has null values for all other fields. The first time you call a non-ID getter (like getName()), the proxy triggers a database hit to “initialize” itself.
- Placeholder Object: Returns a Proxy. Never returns
null. - Deferred SQL: No
SELECTis fired until you call a method other than the ID getter. - Exception on Missing Record: If the ID doesn’t exist in the DB, initialization throws
ObjectNotFoundException.
try (Session session = sessionFactory.openSession()) {
Transaction tx = session.beginTransaction();
// No SQL query fired here — only a Proxy object is created
User userProxy = session.getReference(User.class, 99L);
System.out.println("Proxy created. ID: " + userProxy.getId()); // No SQL yet
// Output: Proxy created. ID: 99
// SQL fires NOW because we access a non-ID property:
// SELECT * FROM users WHERE id = 99
System.out.println("Name: " + userProxy.getName());
// Output: Name: Bob (if record exists)
tx.commit();
} catch (ObjectNotFoundException e) {
// Thrown during proxy initialization if ID 99 doesn't exist
System.err.println("Error: Entity not found for ID 99");
}
3. The Ultimate Comparison
| Feature | session.get() | session.getReference() (formerly load()) |
| Execution Timing | Immediate | Lazy (Deferred) |
| Database Query | Always fires if not in cache | Only fires when data is accessed |
| Return Type | Actual Entity class | Proxy subclass (HibernateProxy) |
| Missing Record | Returns null | Throws ObjectNotFoundException |
| JPA Equivalent | entityManager.find() | entityManager.getReference() |
| Best Used When | Existence uncertain; data needed immediately | ID is certain; only needed for FK association |
4. Why load() Wins for Relationship Management
This is the key performance optimization. Suppose you want to add a Comment to a Post with ID 500.
// Scenario A: Using get() — wastes a SELECT
Post post = session.get(Post.class, 500L);
// Hibernate fires: SELECT * FROM posts WHERE id=500 <-- unnecessary!
Comment comment = new Comment("Great post!", post);
session.persist(comment);
// Hibernate fires: INSERT INTO comments (text, post_id) VALUES (?, 500)
// Scenario B: Using getReference() — zero extra queries
Post postProxy = session.getReference(Post.class, 500L);
// No SQL fired! Hibernate just creates a Proxy with ID=500 in memory
Comment comment2 = new Comment("Great post!", postProxy);
session.persist(comment2);
// Hibernate fires: INSERT INTO comments (text, post_id) VALUES (?, 500)
// Only 1 query total vs. 2 queries in Scenario A!
In a system processing thousands of inserts per second, saving one SELECT per operation is a significant performance win.
5. Potential Pitfalls
1. LazyInitializationException
This is the most common pitfall with load(). It occurs when:
- You fetch a proxy using
load(). - The
Sessionis closed (transaction ends). - You then try to access a non-ID property on the proxy outside the session.
Fix: Always initialize required proxies within the service layer or use Fetch Joins in HQL/Criteria to eagerly load the data within the open session.
2. Proxy Equality and instanceof Checks
Because load() returns a dynamically generated proxy subclass, standard Java type checks can fail in unexpected ways:
User userProxy = session.getReference(User.class, 1L);
// This is true (proxy IS-A User since it's a subclass)
System.out.println(userProxy instanceof User); // true
// This is FALSE — the proxy class is User$HibernateProxyXxx, not User
System.out.println(userProxy.getClass().equals(User.class)); // false
// CORRECT ways to get the real class:
System.out.println(Hibernate.getClass(userProxy)); // User
// Or to get the actual initialized object:
User realUser = (User) Hibernate.unproxy(userProxy);
System.out.println(realUser.getClass().equals(User.class)); // true
3. Mixing get() and load() in the Same Session
If you call load(User.class, 1L) and then get(User.class, 1L) in the same session, Hibernate returns the proxy created by the first call — the first-level cache is already occupied by the proxy.
Frequently Asked Questions
Q1: Does Hibernate load() always return a proxy?
No. If the entity with that ID is already present in the Session Cache (fetched earlier in the same transaction), load() returns the actual entity. Hibernate is smart enough not to wrap an already-loaded real object in a proxy.
Q2: When should I avoid using load()?
Avoid load() when you need to check whether a record actually exists. Since load() doesn’t hit the DB immediately, you won’t know the record is missing until an ObjectNotFoundException is thrown later, which is much harder to handle than a simple null check from get().
Q3: Is session.load() deprecated in Hibernate 7?
It is not deprecated — it is gone. Hibernate 7.0 removed session.load() entirely, along with save(), update(), and saveOrUpdate(). The JPA-standard session.getReference() is the drop-in replacement and behaves identically. Code that still calls load() will not compile against Hibernate 7.
Q4: Can I convert a Proxy to a real object?
Yes. Use Hibernate.initialize(proxyObject) to force the database hit and populate the object (session must be open), or use Hibernate.unproxy(proxyObject) to unwrap the proxy once it has been initialized.
Q5: Does get() or load() perform better in Hibernate 7?
load() (or getReference()) is faster when you only need the object to set a foreign key relationship, because it avoids the extra SELECT query entirely. However, get() (or find()) is the safer choice when you need to read the entity’s data or verify its existence. The performance difference is most impactful in high-throughput insert/update operations where many parent–child associations are being established.
AI Prompts You Can Use
Prompt 1: Choose Between get() and load() for My Use Case
What it does: Analyses your specific access pattern and recommends whether to use session.get() (immediate SELECT, returns null if not found) or session.load() / EntityManager.getReference() (returns a proxy, throws exception if not found), with the tradeoffs explained.
When to use it: Optimising association loading where you need a reference to an entity just to set a foreign key, without fetching the entire row.
I need to load a Hibernate 7 entity in this context: [describe your scenario, e.g., "I'm setting the 'author' field on a new Book entity — I only need the Author's ID, not its data"]. Should I use session.get() or session.load() / entityManager.getReference()? Explain which issues a SELECT immediately, which returns a proxy, what happens if the ID doesn't exist, and show the optimised code for my scenario.
Prompt 2: Fix ObjectNotFoundException Thrown by load() / getReference()
What it does: Explains why load() throws ObjectNotFoundException only when the proxy is initialised (not when load() is called), and shows how to handle the deferred exception safely.
When to use it: When you get an unexpected ObjectNotFoundException at a point in code far from where load() was called.
I'm getting a Hibernate ObjectNotFoundException at an unexpected point in my code after calling session.load(). Explain: why does load() not throw immediately even if the ID doesn't exist? At what point does the proxy initialisation happen and the exception surface? Should I switch to get() with a null check, or use Hibernate.isInitialized() to detect an uninitialised proxy? Show the safe pattern.
Prompt 3: Optimise Bulk Association Setting with getReference()
What it does: Rewrites a data-import or batch-creation method that currently loads full entities just to set foreign keys, replacing them with entityManager.getReference() proxy calls to eliminate unnecessary SELECT statements.
When to use it: When profiling shows N SELECT statements for parent entities that are only needed as FK references when creating child entities.
This Hibernate 7 method loads parent entities just to set a foreign key on child entities, causing N unnecessary SELECT statements: [paste method]. Rewrite it to use entityManager.getReference(ParentEntity.class, parentId) instead of find(), so the parent is a proxy and no SELECT is issued. Show the before and after SQL comparison and explain when this optimisation is safe vs. risky.
Prompt 4: Compare First-Level Cache Behaviour Between get() and load()
What it does: Demonstrates how the first-level cache behaves differently for entities loaded via get() vs proxies created by load(), including what happens when you call both for the same ID within the same session.
When to use it: When debugging cache hits/misses or understanding why a proxy-typed reference and a direct reference behave differently in the same session.
Explain how Hibernate 7's first-level cache interacts with get() vs load(). If I call load(User.class, 1L) first and then get(User.class, 1L) in the same session, is a SELECT issued for the second call? What if I do it in reverse order — get() first, then load()? Is the proxy returned by load() the same object instance as the entity returned by get() for the same ID?
Prompt 5: Detect and Eliminate N+1 SELECT Caused by Lazy Proxy Initialisation
What it does: Identifies the N+1 query pattern that occurs when a collection of entities is loaded and each entity’s lazy-loaded association is individually initialised, and refactors the query to use JOIN FETCH to load everything in one SQL statement.
When to use it: When logs show many sequential SELECT statements for the same entity type after loading a parent list.
My Hibernate 7 query loads a list of orders and then accesses each order's customer, causing N+1 SELECT statements. Query: [paste HQL/JPQL]. Show me how to rewrite this using JOIN FETCH to load orders and their customers in a single SQL. Also show the @EntityGraph alternative. Explain the tradeoff between JOIN FETCH and @BatchSize for large collections.
Conclusion
The choice between get() and load() comes down to one question: do you need the data right now, or are you just creating an association? Use get() (JPA: find()) when you genuinely need to read or verify the entity’s fields — it is safe, predictable, and null-friendly. Use load() (JPA: getReference()) when you know the record exists and only need it to wire up a foreign key — it skips the database entirely and gives your application a meaningful performance boost at scale. Understanding this distinction is one of the most impactful micro-optimisations you can apply across a Hibernate-based application.
Further Reading & Cross-References
- 📘 EntityManager.find() vs. getReference() — the JPA-standard equivalents of get() and load()
- 📘 Hibernate 7 Proxies and LazyInitializationException — how to handle proxy pitfalls in production
- 📘 Master the Hibernate 7 Entity Lifecycle — persistent, transient, detached, and removed states
- 📘 Hibernate 7 First Level Cache — how the Session cache affects get() and load() behaviour
- 🔗 Official Hibernate 7 User Guide — Obtaining references