What do LockModeType.OPTIMISTIC and OPTIMISTIC_FORCE_INCREMENT do, and how do they differ from plain @Version behavior?
answer
- OPTIMISTIC = version check even on pure read (alias READ)
- FORCE_INCREMENT = bump version even if unmodified (alias WRITE)
- aggregate root: child change -> bump root version
- both need @Version, take NO db lock
- vs plain @Version: only checks when dirty
basics
~20 sOPTIMISTIC forces a version check at commit even if you only read the entity (guarding against concurrent changes). OPTIMISTIC_FORCE_INCREMENT additionally bumps the entity's @Version even when the entity itself wasn't modified — useful to signal an aggregate changed.
solid answer
~40 sPlain @Version only triggers a version check when an entity is *dirty*. LockModeType.OPTIMISTIC (JPA-1 alias READ) makes Hibernate verify the version at flush/commit even for an entity you only *read*, throwing OptimisticLockException if another transaction changed it meanwhile — it turns a read into a repeatable-read guarantee at the application level. OPTIMISTIC_FORCE_INCREMENT (alias WRITE) goes further: it *increments* the @Version column even if the entity wasn't modified. The classic use is aggregate consistency — when you modify a child entity, force-increment the aggregate root's version so any concurrent transaction editing a different part of the same aggregate detects the conflict. Both require a @Version field and take no database lock; they are purely version-based. You request them via @Lock on a repository method, exactly like the pessimistic modes.
code
java · 13 linesinterface OrderRepository extends JpaRepository<Order, Long> {
// Just verify nobody else changed this order before we commit:
@Lock(LockModeType.OPTIMISTIC)
@Query("select o from Order o where o.id = :id")
Optional<Order> findByIdChecked(@Param("id") Long id);
// Bump the aggregate root's @Version because a child line changed,
// so concurrent edits to other lines of the same order conflict:
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)
@Query("select o from Order o where o.id = :id")
Optional<Order> findByIdForVersionBump(@Param("id") Long id);
}go deeper
Awareness only: there are optimistic lock modes beyond the plain @Version default.
Distinguish check-only (OPTIMISTIC) from check-plus-bump (FORCE_INCREMENT) and know both need @Version.
Apply FORCE_INCREMENT to aggregate roots for cross-child consistency and reason about the extra UPDATE.
Use these to encode aggregate consistency boundaries in the persistence layer as part of a DDD design.
## The problem plain @Version misses With only `@Version`, Hibernate checks the version **only when the entity is dirty and flushed**. If you *read* an entity and make a decision based on its state — without modifying it — no version check occurs, and a concurrent transaction could change that row after your read but before your commit. Your logic used stale data yet no exception fires. The `OPTIMISTIC` family closes this gap. ## LockModeType.OPTIMISTIC (Alias: the legacy JPA 1.0 name `READ`.) - Requests that Hibernate **verify the version at flush/commit even for an entity you only read**. - Mechanically, at transaction end Hibernate issues a `SELECT ... WHERE id = ? AND version = ?` (or re-reads the version) to confirm the version is unchanged. If it changed → `OptimisticLockException` / Spring `ObjectOptimisticLockingFailureException`. - **No version increment** — the row is not written; it's a *check-only* guard. - Use case: you read entity A, compute a business rule, and write entity B. You want to guarantee A didn't change under you. Locking A with `OPTIMISTIC` enforces that without a DB lock. ## LockModeType.OPTIMISTIC_FORCE_INCREMENT (Alias: legacy `WRITE`.) - Does everything `OPTIMISTIC` does **and forcibly increments the entity's `@Version`** at commit — even if the entity's own fields were untouched. - The bump is a real `UPDATE ... SET version = version + 1 WHERE id = ? AND version = ?`. - **Primary use case — aggregate roots.** In DDD terms, an aggregate is a cluster (root + children) that must stay consistent as a unit. Modifying a *child* row doesn't dirty the root, so two transactions could each edit different children concurrently and both commit, violating a root-level invariant. Force-incrementing the root's version on any child change makes the second committer fail its version check. ## How you request them Same mechanism as pessimistic modes — `@Lock` on a Spring Data repository method: ```java @Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT) @Query("select o from Order o where o.id = :id") Optional<Order> findByIdWithVersionBump(@Param("id") Long id); ``` Or programmatically via `EntityManager.lock(entity, LockModeType.OPTIMISTIC_FORCE_INCREMENT)` / `find(..., lockMode)` / `refresh(..., lockMode)`. ## Prerequisite Both modes **require a `@Version` field** on the entity. Requesting them on an unversioned entity throws `PersistenceException` ("cannot lock ... optimistic") — there is nothing to check or increment. ## No database lock Crucially, neither takes a pessimistic DB lock. They are 100% version-based, so they still scale like optimistic locking — the tradeoff is that conflicts surface as exceptions to retry, not as blocking. ## Alias table (know these) | Modern name | JPA 1.0 alias | |---|---| | OPTIMISTIC | READ | | OPTIMISTIC_FORCE_INCREMENT | WRITE | There is also `PESSIMISTIC_FORCE_INCREMENT`, which acquires an exclusive DB lock *and* bumps the version — the pessimistic analog of force-increment. ## Gotchas - `OPTIMISTIC_FORCE_INCREMENT` writes even on a 'pure read' path — that means an extra UPDATE and it can itself cause an `OptimisticLockException` if the version already moved. - These do not replace `@Version`; they *rely on* it and change *when* it is checked/bumped. - Don't confuse `OPTIMISTIC` (check only) with `OPTIMISTIC_FORCE_INCREMENT` (check + bump) — a very common interview trap. ## When to use - `OPTIMISTIC`: enforce that a read-only-but-decision-critical row didn't change mid-transaction. - `OPTIMISTIC_FORCE_INCREMENT`: keep an aggregate root's version in step with changes to its children, so cross-child concurrent edits are detected.
- Why would you force-increment an aggregate root's version when you only changed a child entity?Modifying a child doesn't dirty the root, so plain @Version wouldn't detect two transactions editing different children of the same aggregate concurrently — both would commit and could violate a root-level invariant. OPTIMISTIC_FORCE_INCREMENT bumps the root's version so the second committer fails its check, preserving aggregate consistency.
- What happens if you request LockModeType.OPTIMISTIC on an entity with no @Version field?It fails — Hibernate/JPA throws a PersistenceException because optimistic lock modes are defined entirely in terms of the version attribute. With nothing to check or increment, the request is invalid.
saying these in an interview costs you the question
- Saying OPTIMISTIC increments the version (it only checks; FORCE_INCREMENT increments).
- Thinking these modes take a database lock like the pessimistic modes.
- Believing plain @Version already checks the version on read-only entities.
- Not knowing READ/WRITE are legacy aliases for OPTIMISTIC/OPTIMISTIC_FORCE_INCREMENT.