The typed OData v4 client allows to build type-safe OData v4 requests for a given service. The Java classes represent the data model and the available operations of the service. As a consequence all requests that are build through the typed OData client are not only syntactically valid but also semantically valid.
The typed OData v4 client consists of service and data model classes. The service classes mirror the API provided by the OData service and serve as entry point for creating requests. They provide a builder which allows for adding further parameters in a fluent way.
To execute HTTP requests the OData client leverages Destinations and are documented in more detail here. The following code snippets assume that such a destination is in place:
On an abstract level requests are generally build up according to the following pattern:
- operation corresponds to the service's capabilities for entities e.g.
- withParameter corresponds to:
- OData v4 query parameters e.g.
- Or other modifiers like custom headers
- OData v4 query parameters e.g.
- Which OData v4 parameters are available depends on the operation. For example when updating entities the
$filterparameter is not available.
Below different OData features are documented using the Business Partner Service on S/4HANA as an example. It is represented by the
BusinessPartnerService class which is part of the pre-generated type-safe client for S/4HANA also known as Virtual Data Model (VDM). The following code snippets assume that an instance of this service is set up:
Create, Read, Update and Delete operations on entities are build from the associated service class:
Each of the above statements returns a builder object that allows for specifying certain request parameters, depending on the operation.
The following query parameters and request options are available for these operations:
$searchare available on reading a single or multiple entities
$orderbyare available only when reading a collection of entities
- Update operations allow to set either
replacingEntity()which will result in
HTTP PUTrespectively. By default entities are modified via
- Update and delete operations allow to modify how ETags are handled:
- By default an ETag is send if one is present on the entity being modified.
matchAnyVersionIdentifier()will instead always send a
*which acts as a wildcard to match all ETags.
ignoreAnyVersionIdentifier()will ensure that no ETag is sent.
- All operations allow for adding custom headers via
An ETag is a version identifier which is often used to implement an optimistic locking mechanism. The SDK will try to read version identifiers from responses and set them when sending OData requests.
Consider the following example:
On the read request the SDK will automatically try to extract the version identifier from the response and store it within the
partner object. When updating it will be taken from there and sent with the
If a service requires this header to be sent: Fetching the entity from the service first is essential to ensure that the ETag is present and up to date.
For create, update and delete requests the SDK will try to send a CSRF token. Upon execution the request will try to fetch a token first before issuing the actual create request. Many services require this behavior for security reasons. However, the create request will be made without a CSRF token if none could be obtained.
When reading entities the API offers
select( ... ) on the builders. Through it the query parameters
$expand are set. It takes in properties of the entity being queried. Primitive properties are added to
$select while complex and navigational properties are added to
$expand. This handling is done automatically by the SDK.
The properties that can be selected or expanded are represented via static fields on the entity class. So there will be a field for each property. E.g. for the business partner entity one can find
The above translates to the following query parameters:
OData v4 allows for formulating nested, fully featured queries on complex and navigational properties. Querying nested objects is possible within expand. That means the following query is possible:
The above translates to the following
expand query parameter:
When operating on a collection of entities the API offers
filter( ... ) on the builders. It directly corresponds to the
$filter parameter of the request. Filters are also build via the static property fields on entities.
The following example:
Will translate to this filter parameter:
Take note of the order of
or is invoked on the result of
and it will form the outer expression while
and is an inner expression in the first branch of
To achieve a different order with
and as the top level statement one would nest the
The OData v4 standard allows for a wide range of filter expressions. A detailed list of what is available in the SDK can be obtained from the Javadoc. The functionality can also be discovered through the fluent API.
The below example leverages OData v4 exclusive features to build a more complex request:
Batch requests allow wrapping multiple OData requests into one single batch call. Thereby we reduce the number of roundtrips to the remote server. Refer to the official OData V4 spec for further reference about batch requests, their semantics and the request/response format.
The described API is subject to change
service object offers the method
batch which allows access to the batch-related methods that help build the batch request.
Multiple single requests (like
createBusinessPartnerRequest) can be wrapped into so-called change sets.
A change set can be added by
addChangeset and the corresponding create, update or delete operations can be passed as partmenters into it. You can wrap multiple change sets into one batch request.
Similarly, retrieve operations can be added to the batch request by using
execute to issue the batch request to the remote system. We receive an instance of
BatchResponse as result object.
getModificationResult to access response for a specific create, update or delete operation.
Similarly, use the method
getReadResult to access response for a specific retrieve request.
As per the OData specification, either all operations or none within the same change set are successful. Hence, if the change set was a failure then evaluating the result of any of the contained requests will contain the failure of the change set.
The Cloud SDK supports different strategies for updating entities which differ in the HTTP method and the payload of the update request.
- Modify Entity (default)
- Replace Entity
The default strategy is the modifying entity update strategy which attempts to modify only the necessary entity fields in the remote system.
It issues a PATCH request and includes only the fields in the request payload whose values were changed by its setter method.
Hence, fields whose values remain unchanged are not sent to the target system.
Calling the method
includingFields(fields ...) instructs the Cloud SDK to add the mentioned fields explicitly in the update request.
This is useful for backend systems which require some unchanged fields in the request payload for given reasons.
This update strategy can be explicitly chosen invoking the method
modifyingEntity() while building the update request.
The replacing entity update strategy attempts to replace the full entity in the remote system. It issues a PUT request and submits all fields in the update request, regardless of any field changes.
This update strategy can be explicitly chosen invoking the method
replacingEntity() while building the update request.
It depends on the capabilities of the specific OData service in the remote system which update strategies are supported in an end-to-end scenario. For example, there are cases where OData services do not support PUT requests.
Sometimes requests fail and the SDK provides a flexible way to deal with such failures on multiple levels. All
execute methods may throw a runtime exception (extending)
This will always contain the request which was (attempted to be) sent out as well as the cause of the exception.
To handle all kind of failures consider the following code:
This handling is the most generic, handling all possible failures.
For more specific information there are dedicated exceptions inheriting from
Please tend to the documentation for all the exception types.
In order to handle different kinds of failure one can list multiple catch clauses to cover different levels or cases that might occur, e.g.:
Note that instead of applying
try/catch one can also make use of
tryExecute on the request builders.
A navigation property describes a unidirectional relationship between two entity types. Like other properties it has a name and declares a multiplicity, i.e. whether to expect a single or multiple values. Additionally a navigation property allows for dedicated CRUD operations, that may not be exposed by default on entity sets of the service root. Such operations also provide a convenient way to access the nested resources of entities.
The typed OData client for OData v4 supports the following operations on (arbitrarily nested) navigation properties:
The below example leverages the creation of a nested entity in relation to an existing entity:
This sample API call translates to the following service request:
Pagination describes the practice of splitting a collection of entities from reading requests into one or many pages. The paging behavior is determined by both server and client.
OData service operators may decide to enable server-driven pagination to limit the amount of data that is fetched and sent over the network to the client, in case generic query options yield huge amounts of data. By splitting the result-set into sequential pages of entities, the data can be requested incrementally. This reduces initial network load and improves overall response times. If the payload of an OData response contains a link including a
$skiptoken, then it indicates a next page to the result-set. The iterable result-set is a consistent snapshot of the data, it can not change between reading individual pages.
In comparison the OData consumer may use query options
$skip (client-driven paging) to read partial data from the result-set. But relying on a consistent state while browsing through the data can be problematic. Between individual requests another user could delete or add an item. This would result in an inconsistent aggregation of data.
By default the SAP Cloud SDK automatically resolves all pages of a result-set if server-driven paging is encountered. For the API consumer it is not necessary to parse the next link and instantiate follow-up requests to aggregate the full result-set.
In case memory efficiency and response time of the consuming application becomes a priority, then the advanced API provides additional means to manually iterate through the internal pages. While accessing the following methods, the internal HTTP requests are executed lazily:
The request builder allows for setting the optional parameter for preferred page size. Please note that the OData service is not obliged to respect this setting.