Optimistic locking with JPA and Hibernate

Imagine having a tool that can automatically detect if you are using JPA and Hibernate properly. Hypersistence Optimizer is that tool!


In this article, we are going to see how optimistic locking version property works when using JPA and Hibernate.

Most often, we overlook basic concepts and focus only on more advanced topics such as associations or queries, without realizing that basic mappings can also have a significant impact when it comes to persistence effectiveness and efficiency.

Lost updates anomaly

Relational database systems have emerged in the time of mainframe systems when the client was interacting with the database from a terminal using a dedicated connection. Back then, when the client needed to operate on the database, a session was opened and all the reads and write happened in the scope of the same database transaction.

This way, the concurrency control mechanism could ensure that reads and writes coming from multiple concurrent clients can be properly interleaved so that they don’t break serializability.

However, this session-based database interaction is no longer the norm, especially when accessing services over the Internet. This is because you can no longer hold onto a database connection across multiple HTTP requests. For this reason, the ACID guarantees no longer hold when your application-level transaction spans over multiple HTTP requests as well as physical database transactions.

One example of an anomaly that can happen because reads and writes happen in different database transactions is the lost update phenomenon.

To understand the lost update anomaly, consider the following example:

Application-level transaction lost update anomaly

The flow of actions happens like this:

  1. Alice loads a Product having a quantity of 5.
  2. Right after, a warehouse batch process updates the Product quantity to 0.
  3. Alice decides to buy the Product, so the quantity will be decreased, resulting in a negative quantity value.

Optimistic locking version property

To address this issue, a @Version column can be used:

@Entity(name = "Product")
@Table(name = "product")
public class Product {

    private Long id;

    private int quantity;

    private int version;

    //Getters and setters omitted for brevity

This way, when the Product entity is updated:

Product product = entityManager.find(


Hibernate includes the version column in the row filtering criteria:

    quantity = 0, version = 2 
    id = 1 AND version = 1

Notice that the version column is set to the next value while the previous value that was read when the entity was fetched from the database is used to filter the record.

So, if the version column value has changed, the UPDATE will not take place and a value of 0 will be returned by the executeUpdate JDBC PreparedStatement method.

When Hibernate reads an update count value of 0, and a javax.persistence.OptimisticLockException will be thrown.

The version property is also used for the DELETE SQL statement, so, if we remove the Product entity:

Product product = entityManager.getReference(


Hibernate executes the following DELETE statement:

    id = 1 AND 
    version = 1

Notice that the version column is used in the WHERE clause to make sure that we are deleting the very same entity snapshot that we have previously read from the database.

Now, back to our previous example, if we are now using a version column, we can prevent the lost update as illustrated by the following diagram:

Application-level transaction prevents lost update using a version column

This time, the flow of actions happens like this:

  1. Alice loads a Product having a quantity of 5.
  2. Right after, the warehouse batch process updates the Product quantity to 0.
  3. Alice decides to buy the Product, so show decreases the quantity and tries to execute the UPDATE.
  4. The UPDATE is prevented because the Product version is no longer 1, and Hibernate throws an OptimisticLockException.

Cool, right?

If you enjoyed this article, I bet you are going to love my Book and Video Courses as well.


Knowing how the optimistic locking version property works is very important when using JPA and Hibernate, as it allows you to prevent the lost update anomaly when a given entity is being modified by multiple concurrent users.

For more details about the lost update phenomenon, and how the database prevents it in the scope of a single database transaction, check out the following article.

Transactions and Concurrency Control eBook

4 Comments on “Optimistic locking with JPA and Hibernate

  1. Hi!

    Good article, thanks! But I still can’t understand, why I get StaleObjectStateException instead of OptimisticLockException? I get StaleObjectStateException if I use the EntityManager directly or when I call Spring Data repository methods. Does this exception depend on Hibernate version or something else?

    • Hibernate wraps the StaleObjectStateException into an OptimisticLockException when bootstrapping via JPA. But this also depends on the Hibernate version you are using.

      • You can find a detailed explanation in the Flushing chapter of my High-Performance Java Persistence book. Enjoy reading it!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.