Handling Concurrent Updates in CAP Java
Share

The Problem: Lost Updates

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.


Why Not Just Lock the Record?

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.

The Solution: Optimistic Locking

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.

  • If the record has been modified in the meantime, the update is rejected to prevent overwriting newer data.
  • If the record is unchanged, the update succeeds and the record’s version is advanced.

This approach avoids long-held locks while still protecting the application from lost updates and unintended data overwrites.

How It Works: ETags

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:

  • Match → The record is unchanged, the update is applied, and a new ETag is generated
  • Mismatch → The record has been modified by someone else, and the request is rejected with 412 Precondition Failed

This ensures updates are applied only to the latest version of the data, preventing silent overwrites.

How CAP Java Does It

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:

image1.png

The managed aspect automatically maintains the modifiedAt timestamp, updating it whenever the record is changed. By annotating this field with @odata.etag, CAP uses it as the entity’s version identifier (ETag).

From that point onward, concurrency control is handled automatically. For every update request, 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.

Seeing It in Action

Let us start the application and create an employee record:

ChatGPT Image May 25, 2026, 08_00_56 PM.png

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:

ChatGPT Image May 25, 2026, 08_04_32 PM.png

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.

ChatGPT Image May 25, 2026, 09_00_35 PM.png

Bob’s request is rejected with 412 Precondition Failed.
Alice’s change is safe.

What Happens Next: Resolving the Conflict

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.

WhatsApp Image 2026-05-25 at 8.40.19 PM.jpeg

Bob resubmits his update using the fresh ETag:  2026-05-25T07:07:15.691711Z

WhatsApp Image 2026-05-25 at 8.42.51 PM.jpeg

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.

Conclusion

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 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.

 

 The Problem: Lost UpdatesConsider 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.Why Not Just Lock the Record?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.The Solution: Optimistic LockingOptimistic 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.If the record has been modified in the meantime, the update is rejected to prevent overwriting newer data.If the record is unchanged, the update succeeds and the record’s version is advanced.This approach avoids long-held locks while still protecting the application from lost updates and unintended data overwrites.How It Works: ETagsOptimistic 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:Match → The record is unchanged, the update is applied, and a new ETag is generatedMismatch → The record has been modified by someone else, and the request is rejected with 412 Precondition FailedThis ensures updates are applied only to the latest version of the data, preventing silent overwrites.How CAP Java Does ItCAP 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, CAP uses it as the entity’s version identifier (ETag).From that point onward, concurrency control is handled automatically. For every update request, 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.Seeing It in ActionLet us start the application and create an employee record:As we can see the response contains the ETag value: 2026-05-25T06:55:44.400909ZLet 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.What Happens Next: Resolving the ConflictA 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.691711ZThis 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.ConclusionConcurrency-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 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. Read More Technology Blog Posts by SAP articles 

#SAPCHANNEL

By ali

Leave a Reply