๐Ÿ”ด The Error You're Seeing

Confirm this matches your console output. If it does, you're in the right place.

ERROR LOG2026-02-18 14:05:55.800 ERROR 8842 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)] with root cause org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.devinhyderabad.entity.User#1]

โšก Quick Fix Works 80% of the time

Implement a retry mechanism using Spring Retry to catch the exception and try again.

@Retryable(value = ObjectOptimisticLockingFailureException.class, maxAttempts = 3) @Transactional public void updateUser(Long id, UserRequest req) { ... }

๐Ÿง  Why this Happens

Tap to expand the deep technical explanation

You used `@Version` for optimistic locking. User A and User B both loaded the same row. User A saved their changes first, incrementing the version. When User B tried to save, Hibernate noticed the version in the DB was higher than the version User B had, meaning the data was stale.

The HITEC City Parking Spot Analogy:

Imagine editing a Google Doc. If someone else starts editing the same paragraph, Google Docs locks it and tells you to refresh. Optimistic locking is the database equivalent of that 'refresh' warning.

๐Ÿ” How to Reproduce Confirm this is your error

Add a `@Version` field to an Entity. Fetch a record in two separate threads. Save the first thread. Wait 1 second. Save the second thread. The second save will throw this exception.

๐Ÿ› ๏ธ Solutions (5 Ways to Fix)

Solution 1โœ“ Most common cause

Implement Retry Logic

๐Ÿ‘‰ Use this if concurrent updates are rare but possible.

Use Spring Retry to automatically catch the exception and retry the transaction 2-3 times before failing.

@Retryable(value = ObjectOptimisticLockingFailureException.class, maxAttempts = 3, backoff = @Backoff(delay = 100)) @Transactional public void updateUser(Long id, UserRequest req) { User user = repo.findById(id).orElseThrow(); user.setName(req.getName()); repo.save(user); }
Solution 2

Fetch fresh data before update

๐Ÿ‘‰ Use this if users are keeping forms open for too long.

Instead of passing a detached entity from the frontend, fetch the fresh entity from the DB inside the @Transactional method, update it, and save.

@Transactional public void updateUser(Long id, String newName) { User user = repo.findById(id).orElseThrow(); user.setName(newName); repo.save(user); }
Solution 3

Use Pessimistic Locking

๐Ÿ‘‰ Use this if concurrent updates are highly frequent and retrying is too expensive.

Use `@Lock(LockModeType.PESSIMISTIC_WRITE)` to lock the row at the database level, forcing other transactions to wait.

@Repository public interface UserRepository extends JpaRepository<User, Long> { @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("SELECT u FROM User u WHERE u.id = :id") User findUserForUpdate(@Param("id") Long id); }
Solution 4

Fix unsaved-value mapping

๐Ÿ‘‰ Use this if you are the only user and still getting this error.

If your `@Version` field is an `int` and defaults to `0`, but Hibernate expects `null` for unsaved values, it gets confused. Use `Integer` (wrapper) instead of `int` (primitive).

@Entity public class User { @Version private Integer version; // Use Integer, not int }
Solution 5

Handle the exception globally

๐Ÿ‘‰ Use this to return a clean 409 Conflict HTTP status to the client.

Catch the exception and tell the user their data is stale.

@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(ObjectOptimisticLockingFailureException.class) public ResponseEntity<String> handleConflict() { return ResponseEntity.status(HttpStatus.CONFLICT).body("Record was updated by another user. Please refresh."); } }

๐Ÿ“‹ Version Notes

Spring Boot 2.x

Uses Hibernate 5. Standard @Version support.

Spring Boot 3.x

Uses Hibernate 6. Better retry integration.

๐Ÿ›ก๏ธ How to Prevent This Next Time

Always keep transactions short and use `@Version` for entities that are frequently updated by multiple users.

Course Search
Search across all chapters & stages
๐Ÿ“–

Search the course

Type any topic โ€” branching, stash, rebase, hooks โ€” and jump straight to that chapter.

merge branchesgit stashundo commitrebase