SAP technical topicObjectETag and concurrency control in ODataModuleRAP_CDS_ODATA

ETag Based Optimistic Concurrency in OData

An ETag is a version marker the server returns with an entity, derived from a field such as a last-changed timestamp or version counter. The client must echo it back via an If-Match header on update or delete. If the current server value no longer matches, the request is rejected with 412 Precondition Failed, meaning someone else changed the record since it was read.

This page covers how ETag-based optimistic concurrency works in OData services built on RAP and CDS, including how the ETag field is declared, how managed and unmanaged scenarios differ in enforcing it, and the practical failures that show up when it is missing, mis-chosen, or misunderstood as a locking mechanism.

Published 16 Sept 2026· 1,441 words

What it is

An ETag is an opaque value the server attaches to a representation of an entity at the moment it is read, typically derived from a last-changed timestamp or an explicit version number field. When the client later sends an update or delete, it must include that value in an If-Match header. The server recomputes the current value and compares it; a mismatch means the entity changed between read and write, and the request is refused with a 412 status instead of silently overwriting someone else's change. The structural fact that causes most confusion: nothing about this is automatic just because the service is OData. In RAP the ETag is only active if a specific field on the CDS projection is explicitly marked as the ETag source and the behavior definition declares it as the master etag field. If that annotation is absent, the service has no concurrency protection at all, and it fails silently rather than with an error.

When to use it

Apply ETag concurrency control where more than one user or process can plausibly update the same entity instance between another user's read and write, and where a lost update carries real cost: financial postings, approval-driven master data, shared planning records. It is the wrong tool for pure read services, for append-only child collections where updates to existing rows are rare or meaningless, and for entities already protected by an enqueue lock at the transaction layer, where adding ETag checks on top mostly generates confusing duplicate rejections. It is also unnecessary boilerplate on every single node of a business object just because the root has it; child nodes with their own independent lifecycle need their own consideration, not an automatic copy of the root's field.

How it fits the stack

ETag handling sits in the OData protocol layer, above the RAP behavior definition and the underlying CDS entity, and below the consuming UI or integration client. The value itself is sourced from a real field on the entity, commonly a last-changed timestamp maintained by the framework or an explicit version counter maintained by application logic. Fiori Elements and the standard SAPUI5 OData model handle the client side transparently: they cache the ETag from each GET and attach it automatically to the next PATCH or DELETE for that entity, so front-end developers rarely touch this directly. It replaces the older pattern of custom code that re-read a record before saving just to compare a timestamp manually. In managed RAP, once the annotation exists, the framework performs the comparison itself; in unmanaged RAP, the developer must implement the check inside the save sequence explicitly.

A worked example

A sales order item CDS projection view carries an ETag annotation on its LastChangedAt field, and the behavior definition for the corresponding RAP business object names that field as the etag master field. Two users open the same item in a Fiori Elements object page. User A changes the delivery quantity and saves; the UI sends a PATCH with an If-Match header matching the ETag from the last GET, the update succeeds, and LastChangedAt advances. User B's screen still holds the ETag from before User A's change. User B edits an unrelated field, like a text note, and saves. The PATCH carries the stale If-Match value; RAP compares it against the current LastChangedAt, finds a mismatch, and returns 412 Precondition Failed. The UI surfaces a message telling User B the data has changed and to refresh before retrying. No custom code was written for any of this; it follows directly from the annotation on the CDS field and the behavior definition declaration.

How to choose

  • Choice of etag field: prefer a technical value that changes only when the record actually changes, such as a maintained timestamp or an incrementing version counter, over a business field like status that can change for reasons unrelated to the edit being protected.
  • Managed versus unmanaged: managed RAP performs the comparison automatically once the field is annotated and declared; unmanaged RAP requires the developer to read the current value and compare it explicitly inside the update logic, and skipping this quietly removes concurrency protection.
  • Timestamp granularity: a field with second-level resolution can produce false negatives where two genuinely different updates within the same second appear identical and a real conflict is missed; a version counter avoids this at the cost of one more field to maintain.
  • Draft handling interaction: know whether the entity uses RAP draft; the ETag check typically applies at activation of the draft into the active instance, not on every intermediate draft save, and treating it as if it fired continuously leads to wrong assumptions during testing.
  • Batch and deep insert scope: confirm how a mismatch on one sub-entity in a nested or batched request affects the rest of the request, since silent partial success is a common surprise if this is not verified explicitly.
  • External consumers: if the service is consumed by systems outside SAP UIs, document the If-Match requirement explicitly, since integration developers frequently omit it and either get unexplained 412s or, worse, disable the check by sending a wildcard match.

Common pitfalls

  • No etag field annotated on the CDS entity: the service accepts concurrent overwrites with no error at all, and the problem only surfaces later as an unexplained lost update, not as a bug report against the OData layer.
  • Choosing a business field as the etag source that also changes as a side effect of other processes, generating false positive conflicts on saves that never actually collided with anything.
  • Timestamp resolution too coarse for the update frequency of the entity, causing real conflicts to pass the check undetected because two changes landed within the same timestamp tick.
  • Long-lived UI sessions holding a stale cached ETag, producing a steady trickle of 412 errors that get misdiagnosed as a service defect rather than as expected optimistic concurrency behavior.
  • Batch requests where one sub-request fails the ETag check and the handling of the remaining sub-requests in that batch is not what the developer assumed, leading to partial commits nobody expected.
  • Developers suppressing the check during testing by sending a wildcard If-Match value and leaving that pattern in integration code that reaches production.
  • Adding fields to the underlying structure or changing how the last-changed field is populated without checking whether the etag comparison still behaves as intended afterward.
  • Treating ETag concurrency as equivalent to a database or enqueue lock; it only detects a conflict after the fact between two client-visible reads, it does not prevent two processes from reading and racing to write at the database level.

ECC, S/4HANA and clean core

In the RAP-based, clean core oriented model on S/4HANA, declaring an ETag field on entities exposed for update is standard practice, not an optional extra, and reviewers should expect it on any writable business object. Older Gateway services built with SEGW commonly required the developer to compute and compare the ETag manually inside custom ABAP, which is error-prone and is discouraged now in favor of the declarative CDS annotation plus behavior definition approach, where the framework performs the comparison consistently. No specific release threshold is implied here beyond general availability of RAP on S/4HANA; the point is the shift from manually coded checks to a declared, framework-enforced one.

Whose problem this is

The developer implements the CDS annotation and, in unmanaged scenarios, the comparison logic itself. The architect decides which field represents the meaningful version state for a given entity and whether concurrency control is warranted at all for that node. Functional consultants flag business processes where concurrent edits by different roles are common. Handover documentation should name the etag source field explicitly and describe expected client behavior on a 412 response.

Related SAP objects

Reviewed pages this object connects to in the ERPClimb knowledge graph.

Source: ERPClimb — https://erpclimb.com/sap-technical-topics/etag-and-concurrency-control-in-odataERPClimb is an independent platform and is not affiliated with SAP SE. Reference pages are written and reviewed by SAP consultants for learning and troubleshooting.