Consider two users, Alice and Bob, accessing the same employee record simultaneously.
Alice updates the employee’s email address and saves the record.
Shortly afterward, Bob updates the employee’s last name and saves his changes.
However, Bob’s update was based on an earlier version of the record, prior to Alice’s modification. As a result, his save operation unintentionally overwrites Alice’s update without any conflict detection or warning.
This scenario is known as a lost update - a common concurrency issue in multi-user systems where one user’s changes silently overwrite another’s, leading to data integrity problems.
One way to prevent lost updates is pessimistic locking. As soon as Alice opens the record, the system locks it, preventing others from modifying it until she finishes.
While effective in theory, this approach is poorly suited for web applications. Users may leave forms open for extended periods, causing records to remain locked unnecessarily and blocking other users from making legitimate updates.
Under real-world traffic, long-held locks can quickly become a source of contention, reduced concurrency, and poor user experience.
Optimistic locking takes a different approach to concurrency control. Instead of locking records when they are read, the system allows multiple users to access and modify data concurrently.
The validation happens at save time. Before applying an update, the system checks whether the record has changed since the user originally retrieved it.
This approach avoids long-held locks while still protecting the application from lost updates and unintended data overwrites.
Optimistic locking on the web is implemented using ETags, a standard HTTP mechanism.
When a record is retrieved, the server includes an ETag in the response. This acts as a version identifier that changes whenever the record is updated.
When sending an update, the client includes this ETag in the If-Match header, effectively stating: apply this update only if the record is still on the same version I read.
The server then compares the provided ETag with the current version:
This ensures updates are applied only to the latest version of the data, preventing silent overwrites.
SAP CAP Java handles all of this automatically. We just need to tell the framework which field to use as the ETag.
We can do that with a single annotation in our CDS model:
The managed aspect automatically maintains the modifiedAt timestamp, updating it whenever the record is changed. By annotating this field with @odata.etag, SAP CAP uses it as the entity’s version identifier (ETag).
From that point onward, concurrency control is handled automatically. For every update request, SAP CAP validates the incoming If-Match header against the current value of modifiedAt. If the values differ, the framework rejects the update with an HTTP 412 Precondition Failed response, preventing stale data from overwriting newer changes.
No additional Java code is required.
Let us start the application and create an employee record:
As we can see the response contains the ETag value: 2026-05-25T06:55:44.400909Z
Let us simulate Alice’s request of updating the email with the ETag we just received:
This succeeds. The record is updated and a new ETag is generated.
Now, simulating Bob's request (who is working with the older version) using the older ETag value.
Bob's request is rejected with 412 Precondition Failed.
Alice's change is safe.
A 412 Precondition Failed response is not an error to suppress - it is a signal that the data has changed since it was last read.
At this point, the user can reload the latest version of the data, review the differences, and reapply their changes with full context before saving again.
Hence, Bob retrieves the latest version of the record and obtains the updated ETag value.
Bob resubmits his update using the fresh ETag: 2026-05-25T07:07:15.691711Z
This time it succeeds. Both changes are now in the record. Nothing was lost.
The key insight is that a 412 is not a failure - it is a safety net doing its job. The system caught a potential lost update before it happened and gave Bob the opportunity to resolve it cleanly.
Concurrency-related data integrity issues are particularly problematic because they occur silently. When two users update the same record, the later save succeeds and the earlier change is overwritten without any indication of a conflict.
Optimistic locking is the standard approach to preventing this class of issue, and SAP CAP Java provides built-in support. By adding a simple @odata.etag annotation, the framework can automatically detect stale updates and reject conflicting changes before any data is lost.
It is a minimal change to the data model, but one that significantly improves the consistency and reliability of enterprise applications.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
| User | Count |
|---|---|
| 48 | |
| 48 | |
| 37 | |
| 35 | |
| 30 | |
| 23 | |
| 23 | |
| 22 | |
| 22 | |
| 22 |