Home/Learn/Hibernate & JPA/Pessimistic Locking

Pessimistic Locking

Intermediate
Transactions

PESSIMISTIC_READ (shared lock) and PESSIMISTIC_WRITE (exclusive lock) via EntityManager.lock() or query lock hints prevent concurrent modification at the DB level.

Overview

Pessimistic locking acquires a database lock at read time, preventing other transactions from modifying (or sometimes reading) the locked row until the current transaction commits or rolls back. It is the right choice when contention is high and the cost of an `OptimisticLockException` + retry is unacceptable — for example, inventory reservation where two transactions must not both decide "stock is available". JPA provides two pessimistic modes: `PESSIMISTIC_READ` (shared lock — prevents other writes, allows other reads) and `PESSIMISTIC_WRITE` (exclusive lock — prevents all other reads and writes; maps to `SELECT ... FOR UPDATE` in SQL). `PESSIMISTIC_FORCE_INCREMENT` additionally increments the `@Version` field while holding the lock.

PESSIMISTIC_WRITE with EntityManager and Spring Data

`PESSIMISTIC_WRITE` maps to `SELECT ... FOR UPDATE`. It acquires an exclusive row lock — no other transaction can read or write the locked rows until the current transaction commits. In Spring Data, add `@Lock(LockModeType.PESSIMISTIC_WRITE)` to the repository method.

JPA — PESSIMISTIC_WRITE (SELECT FOR UPDATE)
// EntityManager
@Transactional
public void reserve(Long inventoryId, int qty) {
    // SELECT * FROM inventory WHERE id=? FOR UPDATE
    Inventory inv = entityManager.find(Inventory.class, inventoryId,
                                       LockModeType.PESSIMISTIC_WRITE);
    if (inv.getAvailable() < qty)
        throw new InsufficientStockException();
    inv.setAvailable(inv.getAvailable() - qty);
    // Lock released on transaction commit
}

// Spring Data repository
public interface InventoryRepository extends JpaRepository<Inventory, Long> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("SELECT i FROM Inventory i WHERE i.id = :id")
    Optional<Inventory> findByIdForUpdate(@Param("id") Long id);
}

// Usage (must be inside @Transactional)
@Transactional
public void reserve(Long id, int qty) {
    Inventory inv = inventoryRepo.findByIdForUpdate(id).orElseThrow();
    inv.setAvailable(inv.getAvailable() - qty);
}

PESSIMISTIC_READ and Lock Scope

`PESSIMISTIC_READ` maps to `SELECT ... FOR SHARE` (MySQL/Postgres) — allows other transactions to also acquire a shared lock and read the row, but prevents any exclusive write lock. Use it when you need to prevent writes but allow concurrent reads (e.g., reading a price that must not change during a batch calculation). `PESSIMISTIC_WRITE` with a `NOWAIT` or `SKIP LOCKED` hint is useful for queue-style processing.

JPA — PESSIMISTIC_READ, SKIP LOCKED, NOWAIT
// PESSIMISTIC_READ — shared lock (FOR SHARE)
// Other readers can also acquire shared lock; writers must wait
@Lock(LockModeType.PESSIMISTIC_READ)
@Query("SELECT r FROM Report r WHERE r.status = 'PENDING'")
List<Report> findPendingForRead();

// SKIP LOCKED — for job-queue processing (avoid lock contention)
@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints(@QueryHint(name = "javax.persistence.lock.timeout", value = "-2")) // -2 = SKIP LOCKED
@Query("SELECT j FROM Job j WHERE j.status = 'QUEUED' ORDER BY j.priority DESC")
List<Job> findAndLockNextBatch(Pageable pageable);
// Only returns jobs NOT locked by another transaction — prevents duplicate processing

// NOWAIT — fail immediately if lock not available (vs waiting for timeout)
@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints(@QueryHint(name = "javax.persistence.lock.timeout", value = "0"))  // 0 = NOWAIT
Optional<Inventory> findByIdNowait(@Param("id") Long id);
// Throws PessimisticLockException immediately if another tx holds the lock

Pessimistic vs Optimistic Locking — When to Use Each

The choice between pessimistic and optimistic locking is a contention vs retry trade-off. **Optimistic** (no DB lock, `@Version` field, retry on `OptimisticLockException`) is right for low-contention scenarios — most reads, typical CRUD. **Pessimistic** (DB lock, `SELECT FOR UPDATE`) is right for high-contention scenarios where retry would cause thundering herd, or when the cost of a lost update is too high.

JPA — optimistic vs pessimistic decision guide
// Decision guide:
//
// Use OPTIMISTIC locking when:
// - Low contention: conflicts are rare
// - Short transactions: fast enough that retry is acceptable
// - Read-heavy: most operations read, few write
// - User-facing UIs: show "someone else changed this, please reload"
//
// Use PESSIMISTIC locking when:
// - High contention: many transactions fight for the same row
// - "Lost update" is unacceptable: e.g. two users both reserve the last item
// - Distributed job processing: SKIP LOCKED for worker queues
// - Financial operations: bank balance update, ticket booking

// Example: ticket booking (high contention, cannot retry on user)
@Transactional
public Booking bookTicket(Long seatId, Long userId) {
    // Lock the seat immediately — prevent double-booking
    Seat seat = seatRepo.findByIdForUpdate(seatId).orElseThrow();
    if (!seat.isAvailable()) throw new SeatTakenException();
    seat.setAvailable(false);
    seat.setBookedBy(userId);
    return bookingRepo.save(new Booking(seat, userId));
}

Key Points to Remember

  • 1PESSIMISTIC_WRITE → SELECT FOR UPDATE: exclusive lock, prevents all concurrent reads and writes
  • 2PESSIMISTIC_READ → SELECT FOR SHARE: shared lock, allows concurrent reads, prevents writes
  • 3Must call locking inside a @Transactional method — lock is held until commit/rollback
  • 4SKIP LOCKED (timeout=-2) skips already-locked rows — ideal for distributed job queues
  • 5NOWAIT (timeout=0) throws PessimisticLockException immediately if the row is locked
  • 6Pessimistic is right for high-contention scenarios; optimistic for low-contention with acceptable retry

Interview Questions

Sign in to ask Aria
1

What is the difference between PESSIMISTIC_READ and PESSIMISTIC_WRITE?

MediumOracle
2

What SQL does PESSIMISTIC_WRITE generate and what does it prevent?

EasyInfosys
3

When would you use SKIP LOCKED instead of a regular FOR UPDATE?

HardAmazon
4

Why can't you use pessimistic locking outside of a @Transactional method?

MediumThoughtWorks
5

Given a ticket booking system with high concurrency, would you use optimistic or pessimistic locking and why?

MediumBooking.com

Ask Aria about Pessimistic Locking

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…