Skip to Content
Technical Articles
Author's profile photo Wouter Lemaire

UI5 OdataModel v4 vs v2 – Custom OData requests


I haven’t used the OData v4 model in UI5 Freestyle apps a lot. I had the idea that I would lose the flexibility of  the Odata v2 model (in combination with a JSON model) and this would block me at some point developing UI5 apps.

My main concern was the possibility to create a custom OData request to my backend with the OData v4 model. I was not yet aware of the fact that the OData v4 model allows us to create custom OData requests.

The OData v4 model, just like in V2, has the ability to use direct bindings, which I do recommend. This will take care of many things in regards to fetching and sending data to the backend. Nevertheless, this might not always be enough. Sometimes, because of circumstances, it might be needed to create a custom Odata request to your backend. Without this, at some point you will get stuck.

After sharing my concerns about this to the UI5 team, Margot connected me with the development team of the OData v4 model. They pointed me out that this was already possible since version 1.70… Unbelievable that I missed that… The details on how this can be done is available in the UI5 documentation:

I made a demo project to try different examples for sending custom OData requests to the backend using the OData v4 model. As a starting point, I used a similar project that I used as a demo for the OData v2 model wrapper function, shared in the following blog posts:

– TypeScript version: (GitHub:

– JavaScript version: (GitHub: )

This time I also needed an OData v4 service, the Northwind v4 service does not comply with the OData v4 standard and does not work. As a workaround, I made a CAP project based on the book sample. In this project, I added a UI5 app using TypeScript as a showcase on how to create custom OData requests using the OData Model v4:

The biggest change (compared to V2) is located in the service class: . (The base class for the service, that I used in the OData Model v2, which includes the wrapper function, is not needed anymore. The main goal of this base class and wrapper function was to wrap every custom http request into a promise.) One of the great things about this OData v4 model is that it is using promises and not callbacks, which makes the wrapper obsolete.

It is still a good practice to separate custom OData requests in a Service class/object (even without the base class).


Compare OData v2 Model wrapper with OData v4 Model

The “NorthwindService” class in my OData v2 example contains the same kind of requests as the “BookService” class in my OData v4 example. The “BookService” still contains the OData v2 code in its comments.

Odata v2 Model Wrapper example:

Odata v4 Model example:

Let’s start building the OData requests using the OData v4 model starting from an OData v2 example:

Before comparing both, I have added one thing for the OData v4 model in the constructor: “bindList”

Reusing the same binding will reduce the amount requests to the backend. The “$$getKeepAliveContext” allows to reuse the earlier fetched contexts when querying a specific context (by key) and not send a new request to the backend. (For example in “getBookId”)


Get entitySet

Getting a list of entities is done by using the function “requestContexts” from the listbinding “bookBinding” initialized in the constructor. It will return a promise with a list of bindingContexts that contains all details of an entity like path, properties, …:

I use the map function to return a list of the objects itself and convert it to the correct type.

OData v2 model, I use my wrapper which uses the read function:


Get entitySet with filter

Getting a filtered list of entities is similar to fetching a full list. One remark, I used a new listbinding instance. If we first get the filtered list and afterwards use the same listbinding for getting the full list, we will still receive the filtered list and not the full list.

  • Create a listbinding and immediately apply the filter to it
  • Use “requestContexts” on the listbinding to get the result using promises
  • Just like the get entityset I use the map function to only get back the objects

With the OData v2 model, this is also done with the “read” function by adding additional parameters.

Get entity using keys

For getting a specific entity using the keys we have two options:

– bindContext: This will create a binding context for the provided path of the entity including the keys, for example:  ‘/Books(<id>)’

– getKeepAliveContext:  it will also create a binding context for the provided path of the entity including the keys but it will try to find it in the earlier fetched contexts

The binding context can be used to get the requested object using the function “requestObject”.

OData v2 model: this is also done using the “read” function using the path of the entity including keys

Using sort with skip and top

This next function in my demo app is a bit strange and would normally NOT be done in the UI. I just want to use it as an example for a sort and skip/top.

A sort is similar to a filter, just set the sort to the binding and use “requestContexts”. Skip and Top, is passed in the “requestContexts” function as the first and second parameter.

With the OData v2 model, this is also done with the “read” function by adding additional parameters.

Create entity

Just like for reading data the OData v4 List Binding comes with a function for creating data:

This function will send a POST request to the backend with the object passed as the first parameter. It will not return a promise but a context for the created entity. The context will not contain any object until the entity is created in the backend. To know the create operation in the backend is finished successfully you have to use the function “created”, which returns a promise. Afterwards, the context can be used to access the created entity data.

OData v2 model comes with a “create” function directly on the ODataModel itself.

Delete entity

OData v4 model does not have a function directly on the binding for deleting an entity. If you want to delete an entity, you will first have to fetch the context. From the context it is possible to call the delete function which will generate a DELETE request.

With OData v2 model it is possible to do this directly from the OData model v2.



Updates will be triggered by using the setProperty function on the binding. Depending on the groupid ($auto or api) it will send the update immediately or on submitBatch


More information about custom OData requests with the OData v4 model:



The full demo project is available on GitHub:

You can run the project by first running “npm install” followed by “npm start”.


The idea of this simple application is to provide an example on how to create custom OData requests using the OData v4 model covering different possible scenarios. Starting from simple things like fetching an array of data followed by applying a filter or sort to end up with create and delete.

I know the getBooksNextID and deleteLatestBook are strange functions and would/should never be done this way in a real app. It was just a way to bring in an example for sorting and deleting a book.


We should not hold back to use the OData v4 model! Compared to the OData v2 Model, which we are all familiar with, it might be a big change. On the other hand, it is more consistent compared to how bindings on controls work. For example if you want to apply a filter on a listbinding of control (even in v2) than this uses the same syntax as for a custom OData request in the v4 model.

What I also like is that it using promises instead of callback functions in the v2 model.

Overall the OData model v4 allows you to use OData v4 api’s without any 3th party library to fully benefit of the new functionalities in OData v4.


One more thing, there is a great episode about OData in UI5 on the UI5 NewsCast podcast available. Feel free to listen to this episode here:

Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo DJ Adams
      DJ Adams

      Nice post!

      Author's profile photo Pieter Janssens
      Pieter Janssens

      Great post Wouter, thanks!

      I wasn't aware either and it was a critical factor for me to switch to OData V4 in freestyle UI5 apps.

      Author's profile photo Holger Schäfer
      Holger Schäfer

      Hi Wouter,

      nice blog post.

      After a huge long running project using OData v4 (that i will show on UI5con together with Marcel Wächter), i liked the benefits of v4 more and more, but it took some time to get into it.

      Especially to figure out, that not everything can be done just using bindings.

      Together with SAP, we had to fix multiple issues and introduce new things like $count inline binding instead of using headerContext (Thanks for the valueable support).

      While the mdc controls are still not available, luckily SAP agreed on allowing the usage of macros for Freestyle Apps. In this combination, we can greatly benefit especially with ValueHelps and SideEffects support.

      But one of the nicest features is the support of BI like $aggregations to allow group/count/distinct request without additional server side implementation.

      There are still some open topics (SAP calls it missing features 😄), but that does mean you should not switch over to it, if you have the chance to use it.

      Best regards



      Author's profile photo Mahesh Raghavaraju
      Mahesh Raghavaraju

      Thanks for the blog Wouter Lemaire !!!

      I was looking for method or syntax of odata V4 to update a single entry. TIA.


      BR, Mahesh R.

      Author's profile photo Ahmet Demirlicakmak
      Ahmet Demirlicakmak

      Hi Wouter Lemaire ,

      I have a table which is bound to an entityset. I create a new new context by using the table binding's create method. I use a separate groupId and updateGroupId in my binding to defer the odata requests. When i try to delete the context again using context.delete(), the context is deleted and context.isDeleted() returns true. But the item still exists in the table. How do i removed the deleted context from the binding. I wont be able to refresh the table as i might lose the other items i have added. How can i proceed further here. How do we handle delete for transient entries.

      this._oPITable.getSelectedItems().forEach((item) => {
                      item.getBindingContext().delete("PIApprovalUpdate").then(() => {
                          // item.setBindingContext(null);


      Author's profile photo Mathias Uhlmann
      Mathias Uhlmann

      Hi Ahmet Demirlicakmak,

      sap.ui.model.odata.v4.ODataListBinding#create states "You can call sap.ui.model.odata.v4.Context#delete to delete the created context again. As long as the context is transient and active, #resetChanges and a call to sap.ui.model.odata.v4.ODataModel#resetChanges with the update group ID as parameter also delete the created context together with other changes." (Unfortunately I needed to remove the many links from the quoted text so that I can submit my reply.)

      The idea on our end is that Context#delete is used in controller code, as this allows to delete all rows in a table with the same API. This has been done in our Sales Orders sample. In the sample I can create a new sales order and delete the transient record again. After the deletion it is gone from the table without the need to save.

      Could you please explain "But the item still exists in the table."? At which time are you checking? Synchronously after the delete-call or when the returned promise is resolved? How are you checking that the record is gone?

      Best regards