Virtual Elements in CDS Views
A virtual element is a CDS view field with no database column, populated at runtime by an ABAP calculation class that the SADL layer calls during an OData or RAP read. It only gets a value when accessed through that runtime path; a plain SQL select, ABAP Unit test, or table browser will show it blank, which is the source of most confusion around it.
This page covers what virtual elements in CDS views actually are, when they are the right mechanism versus a push-down calculation in the view itself, and the operational failures that show up once these fields go live behind a Fiori list report or RAP service. It focuses on the runtime dependency that trips most developers: the field only computes inside the SADL request path.
Published 16 Sept 2026· 1,587 words
What it is
A virtual element is a field declared on a CDS view or projection that has no backing database column and no expression evaluated by the database. Instead it is annotated to point at an ABAP class implementing a calculation exit, and that class is invoked by the SADL runtime while assembling an OData or RAP response, receiving the key fields of the requested rows and returning the computed values to be merged in. The one fact that explains most of the confusion: this calculation only happens inside the SADL read path used by OData/RAP services. Query the same CDS view with a plain SELECT, run it through a table function, open it in a data browser, or hit it from a background report, and the virtual element field comes back blank or initial, because none of those access paths go through the exit class. Developers who expect it to behave like a normal computed column in every context are consistently surprised by this.
When to use it
Reach for a virtual element when the value genuinely cannot be expressed inside the CDS view definition itself: something dependent on the current user, the current date and time at the moment of the request, an external system call, or a business rule too irregular for a CASE expression or arithmetic in SQL. Typical cases are a status icon derived from a combination of fields plus a runtime condition, or an overdue flag computed against today's date. Do not use it as a substitute for logic that CASE, arithmetic, currency conversion functions, or a table function can already express inside the view; those execute in the database and are far cheaper. Also avoid it when the field needs to be filtered or sorted by the consuming app, or when the same calculated value is needed outside an OData/RAP context, such as in a background job or an IDoc mapping.
How it fits the stack
Below the virtual element sits the CDS view entity or projection view carrying the annotation, and below that the exit class doing the actual computation. Above it sits the service definition and service binding, or the RAP business object's projection layer, through which the SADL runtime is triggered when an OData or RAP consumer requests the field. It sits alongside, but is not the same mechanism as, RAP determinations: a determination computes and persists a value as part of a transactional save, while a virtual element computes a transient value fresh on every read and never touches the database. It effectively replaces older patterns from classic SEGW-based OData services where the same 'calculate on read' logic lived in generated DPC extension class methods; virtual elements move that logic into a declared, annotated exit class attached directly to the CDS layer instead of hand-written provider class code.
A worked example
A sales order item CDS projection needs an OverdueFlag field that is true when the requested delivery date has passed and the item is still open, a condition not worth persisting because it changes every day without any write happening. The field is declared on the projection with a virtual element annotation and a reference to a calculation class. When a Fiori list report requests the field in its $select, the SADL runtime collects the key fields of the rows on the current page and calls the class once with that batch, not once per row. The class reads the delivery date and status from the keys passed in, applies the date comparison, and returns a table of computed values keyed the same way, which SADL merges back into the OData response before it reaches the UI. Nothing about this logic exists in the database; it recomputes on every page fetch. If the same flag were needed in a background report driving order cleanup, that report would need its own logic, because it never goes through SADL and the virtual element stays blank there.
How to choose
- Push-down first: if the value can be expressed with a CASE expression, arithmetic, a currency conversion function, or an AMDP/table function inside the view, do that instead. A virtual element calls ABAP once per fetch and cannot benefit from database-level optimization; treat it as the fallback, not the default.
- Filter and sort requirements: if the consuming app or end users need to filter or sort on the field, a virtual element is usually the wrong choice, because SADL's support for server-side filtering and ordering on a field with no backing column is limited and inconsistent across UI frameworks. A persisted calculated field or a determination is the safer route if filtering matters.
- Volume and batching: check whether the exit class is written to receive and process the whole requested key set in one call, or whether it accidentally does a database lookup per row inside a loop. The latter passes every functional test and then falls over on a list report returning a few hundred rows per page.
- Reuse outside OData/RAP: if the same computed value is needed in a background job, an IDoc, a classic report, or any code path that does not go through SADL, a virtual element will not deliver it there. Put the logic in a reusable method or a CDS-native calculation instead and call it from both places.
- Draft and RAP interaction: ask whether the field needs to reflect edits made in an unsaved draft before the transactional save happens. Virtual elements recalculate on read, but depending on how the draft data is buffered, the value shown may lag until the draft is actually persisted.
Common pitfalls
- The field shows blank in a data browser, an ABAP Unit test, or a direct SELECT and gets reported as a bug, when in fact it simply was never read through the SADL path that triggers the calculation.
- The field is exposed in $select but the consumer tries to filter or sort on it; behavior ranges from the filter being silently ignored to a runtime error, depending on the framework and annotation configuration, and this is rarely caught until UAT.
- The exit class does a row-by-row database call instead of processing the batch of keys it was handed, which passes fine in development with ten test rows and degrades sharply once a list report returns real production volumes with pagination.
- The annotation still points to a class name after a refactor renamed or moved it; there is no compile-time check tying the two together in the same way a method signature would be checked, so the break only surfaces at runtime.
- In RAP draft scenarios, the virtual element value shown during editing does not match what will be persisted, because the calculation reads committed data rather than the draft buffer, and testers flag it as a data inconsistency.
- Adding a virtual element to a standard SAP-delivered CDS view via append is not a clean path; the correct approach is a view extension with its own registered calculation class, and skipping that distinction causes upgrade conflicts later.
- A change to the underlying exit class's interface or to the SADL runtime version between releases can cause the field to silently stop populating rather than throw a hard error, so this needs an explicit regression check after any upgrade, not just a compile check.
ECC, S/4HANA and clean core
Virtual elements remain a supported mechanism in S/4HANA and are the standard implementation behind several key-user-defined custom fields flagged as calculated rather than stored. For developer-managed extensibility, clean core guidance discourages attaching custom virtual-element exit classes to standard SAP-delivered CDS views through append-style modification; the supported path is a CDS view extension in the customer namespace with its own registered class. In a public cloud ABAP environment, the exit class and any base interface it implements must be built against released APIs, since reliance on internal SADL classes or unreleased interfaces is a known source of upgrade breakage. Directly modifying a standard SAP exit class to add logic is discouraged regardless of environment.
Whose problem this is
This is a developer-level implementation decision, but an architect should sign off on it early, since choosing a virtual element over a push-down calculation affects filtering, sorting, and clean core compliance. Functional consultants flag the business need for a computed, non-persisted field; the handover to development should state the annotation, the exit class name, and any known limitation on filter or sort support for that field.
Related SAP objects
Reviewed pages this object connects to in the ERPClimb knowledge graph.
Source: ERPClimb — https://erpclimb.com/sap-technical-topics/virtual-elements-in-cdsERPClimb is an independent platform and is not affiliated with SAP SE. Reference pages are written and reviewed by SAP consultants for learning and troubleshooting.