๐ด The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
2026-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)
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);
}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);
}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);
}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
}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
Uses Hibernate 5. Standard @Version support.
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.