BackendBench
Coffee

Unravel โ€บ Java concepts โ€บ Persistence and JPA

Persistence

Optimistic and pessimistic locking

Two users editing the same record at the same time is the lost-update problem.

Learn these first

At a glance

Mental model

Two people editing a shared document.

What happens, in order
  1. 1A @Version long version field on the entity is incremented on every updateThe update statement becomes UPDATE ... SET ..., version = 5 WHERE id = ? AND version = 4. If zero rows are changed, someone else got there first and an OptimisticLockException is thrown.
  2. 2The application decides what to do: show the user the conflict, reload and retry, or mergeA retry loop around a service method is common for short operations.
  3. 3Pessimistic locking uses SELECT ..FOR UPDATE: @Lock(LockModeType.PESSIMISTIC_WRITE) on a repository method, or em.find(..., LockModeType.PESSIMISTIC_WRITE). Other transactions block until commit.
  4. 4Optimistic is the right default for web apps (conflicts are rare, no locks are held while a user thinks)Pessimistic suits hot rows such as stock counters or sequence numbers.
Remember
Watch out
No version field on frequently edited entitiesCatching the lock exception and silently overwritingLong-held pessimistic locks while waiting for user inputUpdating rows in different orders in different code paths
Example (highlighted lines explain it)
@Entitypublic class Account {    @Id private Long id;    private BigDecimal balance;    @Version    private long version;}public interface AccountRepository extends JpaRepository<Account, Long> {    @Lock(LockModeType.PESSIMISTIC_WRITE)    @Query("select a from Account a where a.id = :id")    Optional<Account> findForUpdate(@Param("id") Long id);}
  1. line 6 uses Version, the core of Optimistic and pessimistic locking
  2. line 11 uses Lock, the core of Optimistic and pessimistic locking
Read the full explanation (about 2 min)

Two users editing the same record at the same time is the lost-update problem. A version column (optimistic locking) detects the clash when saving; a database row lock (pessimistic locking) prevents it by making the second user wait.

A mental model

Two people editing a shared document. Optimistic: both edit freely, and the second to save is told "someone changed it, reload". Pessimistic: the first person checks the document out and the second cannot open it for writing.

How it works

A @Version long version field on the entity is incremented on every update. The update statement becomes UPDATE ... SET ..., version = 5 WHERE id = ? AND version = 4. If zero rows are changed, someone else got there first and an OptimisticLockException is thrown.

The application decides what to do: show the user the conflict, reload and retry, or merge. A retry loop around a service method is common for short operations.

Pessimistic locking uses SELECT ... FOR UPDATE: @Lock(LockModeType.PESSIMISTIC_WRITE) on a repository method, or em.find(..., LockModeType.PESSIMISTIC_WRITE). Other transactions block until commit.

Optimistic is the right default for web apps (conflicts are rare, no locks are held while a user thinks). Pessimistic suits hot rows such as stock counters or sequence numbers.

Going deeper

Expose the version to clients (an ETag or a field in the DTO) and send it back on update; otherwise you only detect conflicts inside one request, not between a user loading and later saving a form.

Pessimistic locks must be short and acquired in a consistent order, or you create deadlocks. Set a lock timeout hint so a stuck row does not hang threads forever.

Bulk JPQL updates bypass the version column unless you add VERSIONED, so counters updated in bulk need care.

Under the hood

Hibernate adds the version predicate to the SQL and checks the affected-row count after the statement. Reading the version happens at load time and is stored in the entity snapshot; a detached entity brought back with merge carries its old version, which is exactly what lets the conflict be detected.

Common mistakes

  • No version field on frequently edited entities.
  • Catching the lock exception and silently overwriting.
  • Long-held pessimistic locks while waiting for user input.
  • Updating rows in different orders in different code paths.

How Unravel recognises it

When you paste code, Unravel looks for these signs of optimistic and pessimistic locking. Spotting them yourself is a good habit when you read unfamiliar code:

Part of a learning path

Related concepts

Frequently asked questions

What is Optimistic and pessimistic locking?

Two users editing the same record at the same time is the lost-update problem. A version column (optimistic locking) detects the clash when saving; a database row lock (pessimistic locking) prevents it by making the second user wait.

What are the most common mistakes with Optimistic and pessimistic locking?

No version field on frequently edited entities. Catching the lock exception and silently overwriting. Long-held pessimistic locks while waiting for user input. Updating rows in different orders in different code paths.

How can I see Optimistic and pessimistic locking in real code?

Paste your own Java into Unravel and it will point out where optimistic and pessimistic locking appears, explain how it works at the detail level you choose, and draw the flow of the code around it.