Unravel โบ Java concepts โบ Persistence and JPA
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
Two people editing a shared document.
- 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.
- 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.
- 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.
- 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.
- 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
- Hibernate adds the version predicate to the SQL and checks the affected-row count after the statement
@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);}- line 6 uses Version, the core of Optimistic and pessimistic locking
- 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:
- Annotations:
@Version@Lock@OptimisticLocking - Types:
OptimisticLockExceptionObjectOptimisticLockingFailureExceptionOptimisticLockingFailureExceptionLockModeTypePessimisticLockExceptionStaleObjectStateExceptionStaleStateExceptionCannotAcquireLockExceptionLockTimeoutExceptionPessimisticLockingFailureException - Constants:
LockModeType.PESSIMISTIC_WRITELockModeType.OPTIMISTIC
Part of a learning path
- JPA and Hibernate in depth ยท previous: Dynamic queries (Specifications, Example, Querydsl) ยท next: Transaction propagation and isolation
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.