Interview question
Implement an optimistic version conflict in JPA
Carries the version observed by the client into a conditional update and returns an explicit conflict instead of silently overwriting newer data.
TL;DR
Carries the version observed by the client into a conditional update and returns an explicit conflict instead of silently overwriting newer data.
@Version semantics, HTTP preconditions, conditional updates, affected-row checks, lost updates, and conflict handling.
Practice the problem like a real interview: restate, reason, implement, and test.
Update an order status only if it still has the version the client read. The representation exposes a version and the update receives that expected version. A stale update must change no rows and return a conflict response. The successful update increments the version atomically.
client A reads version 4
client B updates version 4 -> version becomes 5
client A updates expected 4 -> 409/412, no overwrite
@Version field on the entity.@jakarta.persistence.Entity
class Order {
@jakarta.persistence.Id private Long id;
@jakarta.persistence.Version private long version;
@jakarta.persistence.Enumerated(jakarta.persistence.EnumType.STRING)
private OrderStatus status;
}
interface OrderRepository extends org.springframework.data.jpa.repository.JpaRepository<Order, Long> {
@org.springframework.data.jpa.repository.Modifying(clearAutomatically = true, flushAutomatically = true)
@org.springframework.data.jpa.repository.Query("""
update Order o
set o.status = :status, o.version = o.version + 1
where o.id = :id and o.version = :expectedVersion
""")
int updateStatus(long id, long expectedVersion, OrderStatus status);
}
@org.springframework.stereotype.Service
class OrderCommands {
private final OrderRepository orders;
@org.springframework.transaction.annotation.Transactional
long changeStatus(long id, long expectedVersion, OrderStatus status) {
int changed = orders.updateStatus(id, expectedVersion, status);
if (changed == 0) {
if (!orders.existsById(id)) throw new OrderNotFound();
throw new StaleOrderVersion(expectedVersion);
}
return expectedVersion + 1;
}
}
The conditional update and existence check are indexed O(log n). Only the conflict path needs the second query; success uses one update.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Practice the next layer of the same subject.
Answer a similar question in a different context.