Hibernate get() vs load(): What Replaced Them in Hibernate 7

Hibernate get() loads an entity at once and returns null for a missing row; load() returns a proxy and fails on first use. In Hibernate 7, load() is removed and get() is deprecated, so we use find() and getReference().

logo

Hibernate get() and load() are the two Session methods that read an entity by its primary key, and get() runs a SELECT at once and returns null when the row is missing, whereas load() returns a placeholder object without any SQL and queries the database only when we read a field. The placeholder is a proxy, a subclass of the entity that Hibernate generates at runtime and that knows only the id. In Hibernate 7, Session.load() is removed and Session.get() is deprecated, so new code uses their Jakarta Persistence equivalents find() and getReference(), which behave the same way.

We use get() (find() in Hibernate 7) to read the data of a parcel, and load() (getReference() in Hibernate 7) when we need the parcel only as a foreign key, for example to save a scan event.

The following example reads a Parcel with the Hibernate 7 methods, with the old names in the comments.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String trackingCode;
private String status;
private double weightKg;
Parcel parcel = session.find(Parcel.class, 1L);                     // was get(): select ... where p1_0.id=?
Parcel ref = session.getReference(Parcel.class, 1L);                // was load(): no SQL, ref is a proxy
String status = ref.getStatus();                                    // select ... where p1_0.id=?, "in transit"

Parcel missing = session.find(Parcel.class, 99L);                   // null
String gone = session.getReference(Parcel.class, 99L).getStatus();  // EntityNotFoundException

Notice that the missing parcel 99 gives null from find(), whereas getReference() throws only at the first getter.

Next, we look at the Hibernate 7 replacements for both methods and run every old call next to its replacement in a parcel tracking app.

1. What Did get() and load() Do?

Both methods take the entity class and an id. They differ in when Hibernate sends the SELECT to the database.

  • The method get() sends the SELECT inside the call and returns a fully loaded Parcel.
  • The method load() sends nothing. It returns a Parcel$HibernateProxy whose id is set, and runs the SELECT when we call a getter other than getId().

Their Hibernate 7 replacements keep the same timing, as the lookup of parcel 1 shows with both pairs of names.

Timeline for parcel id 1 with three moments: the call, getId and getStatus; the lane for session.get, deprecated in 7.0 and replaced by session.find, runs the SELECT at the call and returns the real Parcel; the lane for session.load, removed in 7.0 and replaced by session.getReference, runs no SQL at the call, returns Parcel$HibernateProxy, answers getId from the proxy and runs the SELECT at getStatus; both lanes send the same select statement
The method find() (the old get()) runs the SELECT at the call, whereas getReference() (the old load()) waits until a field is read.

The SQL is identical in both cases, and only the moment differs. The moment of the SELECT decides the other differences.

get(), replaced by find()load(), replaced by getReference()
SQL at the callOne SELECTNone
Returned objectParcelParcel$HibernateProxy (extends Parcel)
Row does not existReturns nullProxy; EntityNotFoundException on the first getter
Read after the Session closesWorksLazyInitializationException
Typical useShow or change the parcelSet a foreign key to the parcel

2. What Replaced get() and load() in Hibernate 7?

Hibernate 7.0 removed Hibernate-specific methods that have a direct Jakarta Persistence replacement. Hibernate 7.0 removed load() “in favor of Session#getReference which have the same semantic”, and deprecated get() in favor of find() instead of removing it, because get() had not been deprecated before. Hibernate 7.1 also deprecated the byId() loader. The Class overloads of find() and getReference() are standard EntityManager methods, so the same code works in plain Jakarta Persistence. The entity-name overloads exist only on Session.

Hibernate 5/6 callStatus in 7.4Hibernate 7 call
session.get(Parcel.class, id)Deprecated (7.0), for removalsession.find(Parcel.class, id)
session.get(entityName, id)Deprecated (7.0), for removalsession.find(entityName, id)
session.get(Parcel.class, id, LockMode.PESSIMISTIC_WRITE)Deprecated (7.0), for removalsession.find(Parcel.class, id, LockModeType.PESSIMISTIC_WRITE)
session.load(Parcel.class, id)Removed (7.0)session.getReference(Parcel.class, id)
session.load(entityName, id)Removed (7.0)session.getReference(entityName, id)
session.load(parcel, id)Still availableNo change
session.byId(Parcel.class).load(id)Deprecated (7.1), for removalsession.find(Parcel.class, id)
session.byId(Parcel.class).getReference(id)Deprecated (7.1), for removalsession.getReference(Parcel.class, id)
catch (ObjectNotFoundException e)No longer thrown herecatch (EntityNotFoundException e)

The deprecated methods still compile with a removal warning, and they send the same SQL as their replacements. Code that calls load(Parcel.class, id) no longer compiles, because the only load() left on Session is the one that fills an object we pass in.

3. get() vs load() Example

The following example is a parcel tracking app that runs on Hibernate 7.4.11, Java 25 and an in-memory H2 database, and it runs every old call next to its replacement.

3.1. Parcel Tracking Model

A Parcel has a tracking code, a status and a weight. A ScanEvent records that a depot scanned a parcel. Its parcel field is a lazy @ManyToOne, so the ScanEvent table stores only the parcel’s id in parcel_id.

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String location;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parcel_id")
private Parcel parcel;

The data is one parcel with id 1, tracking code “lisbon-1”, status “in transit” and a weight of 2.5 kg. We get a Session from the SessionFactory with sf.inTransaction(session -> …), which opens the session, starts a transaction and commits it at the end.

3.2. Loading a Parcel With find() Instead of get()

The method find() is the one to call where old code called get(). The deprecated get() still works in 7.4 and prints the same statement.

Parcel parcel = session.get(Parcel.class, id);    // deprecated
// select p1_0.id,p1_0.status,p1_0.trackingCode,p1_0.weightKg from Parcel p1_0 where p1_0.id=?

Parcel parcel = session.find(Parcel.class, id);
// select p1_0.id,p1_0.status,p1_0.trackingCode,p1_0.weightKg from Parcel p1_0 where p1_0.id=?
Class<?> type = parcel.getClass();                // class Parcel
String status = parcel.getStatus();               // "in transit", no SQL

The object is the real Parcel class with every column already read, so we can use it after the session closes.

3.3. Getting a Proxy With getReference() Instead of load()

The method getReference() replaces load(Parcel.class, id). The call returns at once, and the SELECT appears only at getStatus().

Parcel parcel = session.getReference(Parcel.class, id);   // no SQL
String className = parcel.getClass().getName();           // com.howtodoinjava.hibernate.getload.Parcel$HibernateProxy
Long parcelId = parcel.getId();                           // 1, no SQL
boolean loaded = Hibernate.isInitialized(parcel);         // false

String status = parcel.getStatus();                       // select p1_0.id,p1_0.status,p1_0.trackingCode,p1_0.weightKg from Parcel p1_0 where p1_0.id=?
                                                          // "in transit"
boolean loadedAfter = Hibernate.isInitialized(parcel);    // true

The method Hibernate.isInitialized() tells us whether the proxy has loaded its row yet. A proxy can load only while its Session is open. Read after the transaction, the proxy throws an exception.

Parcel parcel = sf.fromTransaction(session -> session.getReference(Parcel.class, id));
String status = parcel.getStatus();   // LazyInitializationException
org.hibernate.LazyInitializationException: Could not initialize proxy [com.howtodoinjava.hibernate.getload.Parcel#1] - no session

The exception is the same LazyInitializationException that lazy associations throw. We can load a proxy on purpose with Hibernate.initialize(), and Jakarta Persistence 3.2 adds new options to find() and getReference().

3.4. What Happens When the Parcel Does Not Exist

The missing row is the difference most developers search for. The methods get() and find() return null, whereas load() and getReference() never check the row at the call, so the error comes later.

Three rows for a missing parcel id 99: find, formerly get and byId().load, runs a SELECT at the call and returns null; getReference, formerly load and byId().getReference, runs no SQL, returns a proxy with getId 99, and the first getter getStatus runs a SELECT and throws EntityNotFoundException; load(new Parcel(), 99L) runs a SELECT at the call and throws EntityNotFoundException; a note says Hibernate 6 threw org.hibernate.ObjectNotFoundException for load and getReference, while Hibernate 7 throws jakarta.persistence.EntityNotFoundException
A missing row gives null from find() and an exception from getReference(), at the first getter instead of at the call.
Parcel viaGet = session.get(Parcel.class, 99L);            // select ... where p1_0.id=?   null
Parcel viaFind = session.find(Parcel.class, 99L);          // select ... where p1_0.id=?   null
Parcel viaById = session.byId(Parcel.class).load(99L);     // select ... where p1_0.id=?   null

Parcel parcel = session.getReference(Parcel.class, 99L);   // no SQL, no error
Long parcelId = parcel.getId();                            // 99
String status = parcel.getStatus();                        // select ... where p1_0.id=?, EntityNotFoundException
jakarta.persistence.EntityNotFoundException: No row with the given identifier exists for entity [com.howtodoinjava.hibernate.getload.Parcel with id '99']

The exception type changed between versions. In Hibernate 6.6, load() and getReference() threw org.hibernate.ObjectNotFoundException with the message “No row with the given identifier exists”. Hibernate 7.4 throws EntityNotFoundException from jakarta.persistence, and the two classes are unrelated, so an old catch (ObjectNotFoundException e) block no longer catches it.

3.5. Recording a Scan Without Loading the Parcel

Recording a scan is the reason load() existed. To save a scan, Hibernate needs only the parcel’s id for the parcel_id column. With getReference(), saving the scan is one statement.

session.persist(new ScanEvent("Lisbon depot", session.getReference(Parcel.class, id)));
// insert into ScanEvent (location,parcel_id,id) values (?,?,default)

With find(), Hibernate first reads a parcel we never use.

session.persist(new ScanEvent("Porto depot", session.find(Parcel.class, id)));
// select p1_0.id,p1_0.status,p1_0.trackingCode,p1_0.weightKg from Parcel p1_0 where p1_0.id=?
// insert into ScanEvent (location,parcel_id,id) values (?,?,default)

With getReference(), every scan saves one SELECT, which adds up when a depot records thousands of scans. When the id comes from user input, we keep in mind that getReference() does not check it, so the foreign key on parcel_id is the only check.

3.6. Filling an Existing Object With load(parcel, id)

The third load() signature from Hibernate 5 is still on Session in 7.4 and is not deprecated. It reads the row into an object we created.

Parcel parcel = new Parcel();
session.load(parcel, id);                 // select p1_0.id,p1_0.status,p1_0.trackingCode,p1_0.weightKg from Parcel p1_0 where p1_0.id=?
String code = parcel.getTrackingCode();   // "lisbon-1"

session.load(new Parcel(), 99L);          // EntityNotFoundException

Unlike the old load(Class, id), this one runs the SELECT at the call. We rarely need it, because find() returns a new object and is the standard way.

The complete project on GitHub contains every old and new call from this section, prints the SQL and includes 23 JUnit tests (mvn -q compile exec:java, mvn test).

4. get() vs load() FAQs

4.1. When Should I Use get() and When load()?

We use get() when the code reads the parcel’s data, and load() when the code needs the parcel only as a reference by id. In Hibernate 7, the same rule applies to the new names.

TaskHibernate 7 method (old name)
Show the parcel’s status on a tracking pagefind() (get())
Check whether a tracking id existsfind() and compare with null (get())
Change the status to “delivered”find() (get())
Save a scan that points to the parcelgetReference() (load())
Return the parcel from a service after the transactionfind() (get())

When in doubt, use find(). A null check is easier to handle than an exception that appears later at a getter call.

4.2. Does get() Always Hit the Database?

No. Both get() and find() first look in the persistence context, the entities the current Session already holds. A second lookup of the same id in the same session runs no SQL.

Parcel first = session.find(Parcel.class, id);   // select ... where p1_0.id=?
Parcel second = session.get(Parcel.class, id);   // no SQL, second == first

The same applies across the two method families. After getReference(), a find() for that id returns the proxy itself, which is loaded by then.

Parcel ref = session.getReference(Parcel.class, id);   // no SQL
Parcel found = session.find(Parcel.class, id);         // select ... where p1_0.id=?
boolean same = found == ref;                           // true, found is the proxy
boolean loaded = Hibernate.isInitialized(found);       // true

4.3. How Do I Load a Parcel With a Lock?

Old code passed a LockMode to get(). The method find() takes the standard LockModeType, and Hibernate’s own LockMode also works, because in Hibernate 7 it implements FindOption. Both lines print the same statement.

Parcel locked = session.get(Parcel.class, id, LockMode.PESSIMISTIC_WRITE);   // deprecated
Parcel lockedToo = session.find(Parcel.class, id, LockModeType.PESSIMISTIC_WRITE);
// select p1_0.id,p1_0.status,p1_0.trackingCode,p1_0.weightKg from Parcel p1_0 where p1_0.id=? for update

4.4. What Entity Name Do get(String, id) and load(String, id) Expect?

The default entity name is the fully qualified class name, not the simple name. The methods find(String, id) and getReference(String, id) take the same value.

Object parcel = session.find("com.howtodoinjava.hibernate.getload.Parcel", id);   // the parcel
Object unknown = session.find("Parcel", id);                                       // UnknownEntityTypeException: Unknown entity type 'Parcel'

With a normal @Entity class, the Class overload is the better choice, because the compiler checks the class name and the return type.

5. Conclusion

The old get() read the row at once and returned null for a missing id, whereas load() returned a proxy and read the row on first use, throwing an exception if the row was missing. In Hibernate 7, find() replaces get() and getReference() replaces the removed load(), with the same SQL timing, and a missing row raises jakarta.persistence.EntityNotFoundException instead of ObjectNotFoundException. We use find() to read or change a parcel, and getReference() when we need the parcel only as a foreign key.

6. References

Happy Learning !!

Source Code on Github

Leave a Comment

  1. Hi Lokesh,
    Many online tutorials say “get() method always hits the db”.
    but i dont see in the console the query triggered again if use get() twice.
    how far is this statement true?

  2. short code | Stuatus | A(Sample2) | B(Sample2) | C(Sample2) | DSample2) | E (Sample2) | A(All Sample3) |
    11111 pending New New New New New New
    22222 under process New – New New –

    In the above Sample format it may helps for you (A,B,C under Sample1)(C,D are under Sample2),(A is the Sample3.The Sample3 have some status but here we are showing only on Carrier Staus that is the Default Status and some where i mentioned (‘-‘) that means the status we didn’t selected and observe for same alsi Sample3(‘-‘) but here i already select some status(b,c,d..) status except “A” But still it is showing the blank value.My question is inplace of bydefault status i want to display the status whatever we selected.I hope you understand.

  3. Hi,

    I created one report.In reports there are three samples.sample1,sample2,sample3.When I run the report it is generated the XML.In that XML it will display all the Sample reports Status’s.But my question is in Sample1,Sample2,Sample3 there are some carriers list with check boxes .For example assume in Sample3 have some Carrier status with the names of A,B,C and so on.. with check boxes.First Carrier Status is by default i.e (“A”) and it is coming from DB remaining all are optional. When I am selecting the first Carrier Status in the generated XML the Carrier status is showing properly which we are selected that is fine.But the problem is when I selected all the Carrier(“b”,”c”..so on) except first Carrier (“A”) in the XML it is not showing the selected Carrier Status..Can you please tell me how to resolve this.Here is the Sample code.here Sample3CarrierNameList is Sample3 list i.e(a,b,c..) ,carrier means single carrier name(“a”…)

    for (Object resultRow : returnList) {
    Object[] rowResultSet = (Object[]) resultRow;

    Map rowMap = new LinkedHashMap();

    // rowMap.put(“reqProvisioningId”, rowResultSet[0].toString());
    rowMap.put(“reqDescription”, rowResultSet[0].toString());
    rowMap.put(“reqShortCode”, (String) rowResultSet[1]);
    rowMap.put(“reqStatusText”, (String) rowResultSet[2]);
    rowMap.put(“reqAssignedToInternal”, (String) rowResultSet[3]);
    int index = 4;

    //CMP 1.2 Upgrade. Fix for “Carrier Status of Requests For Customer” report
    //not displaying all tier 3 carrier status when carrier’s are renamed.
    boolean isTier3Populated = false;
    for (String carrier : carrierNameList) {

    // Now add the first Sample 3 Carrier as a representative of all
    // other Carriers
    if (isSample3Populated == false
    && Sample3CarrierNameList.size() > 0
    && Sample3CarrierNameList.contains(carrier)) {
    rowMap.put(Constants.ALL_TIER_3_CARRIER,
    (String) rowResultSet[index]);
    index++;
    isTier3Populated = true;
    } else if (!Sample3CarrierNameList.contains(carrier)) { // Exclude all Tier 3 Carriers Except the First One
    rowMap.put(carrier, (String) rowResultSet[index]);
    index++;
    }

    }

    carrierWiseList.add(rowMap);
    }

    logger.debug(“Carrier wise List:” + carrierWiseList);
    return carrierWiseList;
    }

    Thanks
    Sri.K

  4. Hi LOkesh, good tutorial, can you help with this little problem?
    I hava a CategoryDao class and it has a getCategory() method to fetch a Category by ID. (This method works)

    @Override
    	public Category getById(int id) {
    		return (Category) sessionFactory.getCurrentSession().get(Category.class, id);
    	}
    

    And I need a same method to fetch a Category by its name, so I create a getByName() method but I have the following error:
    org.hibernate.TypeMismatchException: Provided id of the wrong type for class com.sedae.model.Category. Expected: class java.lang.Integer, got class java.lang.String.
    My method to fetch by name is as follow:

    @Override
    	public Category getByName(String name) {
    		return (Category) sessionFactory.getCurrentSession().get(Category.class, name);
    	}
    

    Can you show me where is my error? Thanks in advance…

    • You can’t use any other attribute except primary key… Whether you wan’t to use another attribute you should have to use ‘HQL’.
      Also look at @naturalId…

      Something like

      sql query = “SELECT * FROM Category c where c.name= :name”

      and set the parameter like
      setParameter(“name”,parameterName)

      and get the result with getResultList or getSingleResult (if you are sure that query returns only one record)

Comments are closed.

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.