Spring Boot cookbook · Intermediate
Handle concurrent updates with optimistic locking
Prevent lost updates when two users edit the same record, and retry automatically for background updates.
Approach
Add @Version to the entity. For user edits, return 409/412 and let the user reload (use ETags with the version). For automated updates (counters, stock), retry the transaction a few times on ObjectOptimisticLockingFailureException — from a separate bean so each attempt is a new transaction.
Solution
@Entity
class Inventory {
@Id private String sku;
private int available;
@Version private long version;
void reserve(int qty) {
if (available < qty) throw new InsufficientStock(sku, available);
available -= qty;
}
}
@Service
class InventoryService {
private final InventoryRepository repo;
InventoryService(InventoryRepository repo) { this.repo = repo; }
@Transactional
public void reserve(String sku, int qty) {
repo.findById(sku).orElseThrow().reserve(qty);
}
}
@Service
class ReservationFacade {
private final InventoryService inventory;
ReservationFacade(InventoryService inventory) { this.inventory = inventory; }
public void reserveWithRetry(String sku, int qty) {
for (int attempt = 1; ; attempt++) {
try {
inventory.reserve(sku, qty); // new transaction per attempt (through the proxy)
return;
} catch (ObjectOptimisticLockingFailureException e) {
if (attempt == 3) throw e;
try { Thread.sleep(ThreadLocalRandom.current().nextLong(20, 100) * attempt); }
catch (InterruptedException ie) { Thread.currentThread().interrupt(); throw e; }
}
}
}
}
Pitfalls
- Retrying inside the same transaction (the persistence context is stale).
- Silently overwriting user edits instead of surfacing conflicts.
- High-contention hot rows — consider atomic SQL updates instead.
Variations
@Retryable(Spring Retry, or Spring Framework 7's built-in resilience annotations) on the facade method.- Atomic update:
UPDATE inventory SET available = available - :q WHERE sku = :s AND available >= :q.
Further reading: Spring Data JPA: Persisting entities
Reference: https://docs.spring.io/spring-data/jpa/reference/jpa/entity-persistence.html
More Spring Boot recipes
- Add correlation IDs to Spring Boot logs
- Add retries and a circuit breaker with Resilience4j
- Authenticate machine clients with API keys in Spring Security
- Cache service results in Redis with @Cacheable
- Consume Kafka messages with retries and a dead-letter topic
- Implement soft delete with Hibernate
- Make POST endpoints idempotent in Spring Boot
- Map entities to DTOs with MapStruct