Skip to Content
Technical Articles
Author's profile photo Philipp Herzig

Step 20 with SAP Cloud SDK: Create and Deep Insert with the Virtual Data Model for OData

The following steps will explain how you can use deep insert with the virtual data model for OData to post complex data structures in one API call to SAP S/4HANA.

Note: This post is part of a series. For a complete overview visit the SAP S/4HANA Cloud SDK Overview.


Goal of this blog post

This blog post introduces to you the create and deep insert functionality for OData as supported by the SAP S/4HANA Cloud SDK in more detail. After this blog you will be able to understand

  • How to build up a complex data structure using the virtual data model.
  • How to write deeply nested data to SAP S/4HANA in a single call.
  • How to write tests for deep insertion as unit as well as integration test.


In order to successfully go through this tutorial you have to complete the tutorial at least until

In addition, we will use the virtual data model for OData as introduced in

As we will also utilize mocking for writing our unit tests, you should be familiar, with the basic mocking capabilities of the SDK as introduced in:

In addition, deep insert of S/4HANA APIs works as of SAP Cloud SDK version 1.5.0 and has been improved as of version 1.6.0. Therefore, please make sure, your SDK Bill of Material is updated accordingly like shown below:

    <!-- possibly further managed dependencies ... -->


Deep Insert


Deep Insert is already part of the OData specification, version 2 without this explicit name. Although not supported yet, the OData specification, version 4 is much more explicit on the semantics. Citing from the spec, Deep Insert is defined as:

  • A request to create an entity that includes related entities, represented using the appropriate inline representation, is referred to as a “deep insert”.
  • On success, the service MUST create all entities and relate them.
  • On failure, the service MUST NOT create any of the entities.

This means deep insert is an atomic operation that is either successful or fails for all entities. Furthermore, it is for insert-only operations, i.e., the OData spec does not foresee any “deep update” operation yet (to be fair, it is part of the 4.01 working draft spec, however, we are not aware of any provider implementations yet, in particular as S/4HANA APIs are based on OData V2).


Writing the application code

To get started, we first of all create a new ErpCommand called StoreBusinessPartnerCommand because also write operations shall be contained within Hystrix-based command patterns. If you did our previous tutorials that should be now straightforward to you:

public class StoreBusinessPartnerCommand extends ErpCommand<BusinessPartner> {

    private BusinessPartnerService businessPartnerService;
    private BusinessPartner businessPartner;

    public StoreBusinessPartnerCommand(final ErpConfigContext erpConfigContext, final BusinessPartnerService businessPartnerService, final BusinessPartner businessPartner) {
        super(StoreBusinessPartnerCommand.class, erpConfigContext);
        this.businessPartnerService = businessPartnerService;
        this.businessPartner = businessPartner;

    protected BusinessPartner run() {

        try {

            return businessPartnerService

        } catch (final ODataException e) {
            throw new HystrixBadRequestException(e.getMessage(), e);

Hint: This code goes to your <projectroot>/application/src/main/java folder, either into the default package or a package of your choice.


What the code does

The code shows a new StoreBusinessPartnerCommand which assumes an ErpConfigContext, a BusinessPartnerService and a concrete BusinessPartner instance upon creation time.

Within the run() method, i.e., whenever the command is executed, it calls the businesspartner service with the create method and executes against the current multi-tenant ERPContext as explained in previous tutorials.


The StoreBusinessPartnerCommand takes a businesspartner instance as input. This can be a potentially complex data type. Therefore, in the next step we need to create a nested data structure based on the BusinessPartner data model.

The structure we are interested in is presented below. The root entity will be the business partner which is connected to zero-to-many BusinessPartnerRoles and BusinessPartnerAddresses which is again connected to zero-to-many EMailAddresses:



For this purpose, we are creating a new simple servlet that exposes a POST method to our clients:

public class BusinessPartnerServlet extends HttpServlet {

    private static final Logger logger = CloudLoggerFactory.getLogger(BusinessPartnerServlet.class);

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        final String firstname = request.getParameter("firstname");
        final String lastname = request.getParameter("lastname");
        final String country = request.getParameter("country");
        final String city = request.getParameter("city");
        final String email = request.getParameter("email");

        //do consistency checks here...

        final AddressEmailAddress emailAddress = AddressEmailAddress.builder()

        final BusinessPartnerAddress businessPartnerAddress = BusinessPartnerAddress.builder()

        final BusinessPartnerRole businessPartnerRole = BusinessPartnerRole.builder()

        final BusinessPartner businessPartner = BusinessPartner.builder()

        String responseBody;

        try {
            final BusinessPartner storedBusinessPartner = new StoreBusinessPartnerCommand(new ErpConfigContext(), new DefaultBusinessPartnerService(), businessPartner).execute();
            responseBody = new Gson().toJson(storedBusinessPartner);

        } catch(final HystrixBadRequestException e) {
            responseBody = e.getMessage();
            logger.error(e.getMessage(), e);



What the code does

The code implements a new Servlet exposed under the /businessPartner URL path. It expects five parameters to be set: firstname, lastname, country, city and e-mail. For readability reasons, we omit here details for checking that these parameters are actually set and throw corresponding error messages to the client, an aspect you should definitively do in any productive code.

Based on the five input parameters, we are creating the various entities. First, an entity to store the E-Mail Address using the exposed builder pattern method. Secondly, we create one BusinessPartnerAddress based on the city and country parameter as well as the e-mail address entity from the first step. Thirdly, we create a business partner role using the FLCU01 role (which actually stands for a customer). Fourthly, the final business partner entity which consumes the remaining parameters and the entity from the steps before.

Finally, we use our StoreBusinessPartnerCommand to store the created business partner entity. As a result we will get the stored entity which will be enriched by an ID that is given by the S/4HANA system which then is serialized into a JSON for the client.

In case of an exception, we simply return the error message, ignoring any pretty printing or JSON formatting here for simplicity reasons.

When we deploy the above created code to SAP Cloud Platform or using a local instance (please consider previous tutorials such as Step 3 with SAP Cloud SDK: HelloWorld on SCP CloudFoundry). In this example, we have used a mvn clean install && mvn tomee:run to run it on localhost.

Then we can use a tool like Postman or Curl to check whether the code works. As you can see in this example, the business partner has been successfully posted and contains a BusinessPartner ID and UUID which was enriched by S/4HANA:

Writing a unit test

As learned in Step 19 with the SAP Cloud SDK: Mocking S/4HANA calls or how to develop an S/4HANA extension without an S/4HANA system, we can utilize mocking to test the functionality without an S/4HANA system to achieve code coverage, fast running tests and better testable code.

For this purpose, we are creating the following test class which checks the basic assumptions of our API as well as the failure case:

public class GetBusinessPartnerMockedTest
    private static final MockUtil mockUtil = new MockUtil();

    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
    private BusinessPartnerService service;

    private BusinessPartner alice;

    public void before() {
        mockUtil.mockDestination("ErpQueryEndpoint", URI.create(""));


    public void testBusinessPartnerCreateSuccessful() throws Exception {

        final BusinessPartner enrichedAlice = Mockito.mock(BusinessPartner.class);


        BusinessPartner partner = new StoreBusinessPartnerCommand(new ErpConfigContext(), service, alice).execute();
        assertEquals(enrichedAlice, partner);
        assertEquals("123", enrichedAlice.getBusinessPartner());

    @Test(expected = HystrixBadRequestException.class)
    public void testBusinessPartnerCreateNotSuccessful() throws Exception {

                .thenThrow(new ODataException());

        new StoreBusinessPartnerCommand(new ErpConfigContext(), service, alice).execute();

Hint: This code goes to your <projectroot>/unit-tests/src/test/java folder.

Writing an integration test

Just the unit test might not be sufficient when we want to test the real integration with S/4HANA. Therefore, we would also like to leverage an integration test as used in previous tutorials:

import static com.jayway.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

public class BusinessPartnerDeepInsertTest {
    private static final MockUtil mockUtil = new MockUtil();

    private URL baseUrl;

    public static WebArchive createDeployment() {
        return TestUtil.createDeployment(BusinessPartnerServlet.class,

    public static void beforeClass() throws URISyntaxException {
        mockUtil.mockErpDestination("ErpQueryEndpoint", "S4HANA");

    public void before() {
        RestAssured.baseURI = baseUrl.toExternalForm();

    public void testStoreAndGetCustomers() {

                .parameters("firstname", "John", "lastname", "Doe", "country", "US", "city", "Tuxedo", "email", "")
                    .body("BusinessPartner", not(isEmptyString()))
                    .body("BusinessPartnerUUID", not(isEmptyString()));

Hint: This code goes to <projectroot>/integration-tests/src/test/java. In addition, we are using a system alias “S4HANA” in this example, which is stored inside the<projectroot>/integration-tests/src/test/resources/systems.yml (the basics of the credentials.yml / systems.yml approach was introduced in Step 5 with SAP Cloud SDK: Resilience with Hystrix).

Both tests together give us a code coverage of 91%:


In this tutorial, we have shown how you can leverage the deep insert functionality of the S/4HANA Cloud SDK to easily insert deeply nested data to SAP S/4HANA in a single call. Besides the pure functionality, we have also shown you how to implement unit and integration tests for this functionality.


Reach out to us on Stackoverflow via our s4sdk tag. We actively monitor this tag in our core engineering teams.

Alternatively, we are happy to receive your comments to this blog below.

Assigned Tags

      You must be Logged on to comment or reply to a post.
      Author's profile photo Tri Minh Le
      Tri Minh Le

      Hi Philipp Herzig,

      For example, I already have a business partner ABC and need to create a Business Partner Role entity for this partner.

      How can I implement that logic?




      Author's profile photo Christoph Schubert
      Christoph Schubert

      Hi Tri,

      thanks for your question!

      To add an BusinessPartnerRole to an already existing BusinessPartner you can use the updateBusinessPartner method on the BusinessPartnerService.

      This might look something like this:

      final BusinessPartnerService service; // your BusinessPartner service
      final BusinessPartner abcBusinessPartner; // your ABC BusinessPartner
      final BusinessPartnerRole newRole; // your role, for example created by: BusinessPartnerRole.builder(). ... .build();



      Author's profile photo Meenakshi A N
      Meenakshi A N

      Hi everyone,

      I am doing CRUD Operation for API_PRODUCT_SRV Service which involves deep insert concept.

      Could anyone help on how can I modify the code to make deep insert to work for Product entity.

      I am using cloud SDK instead of servlets.I dont want to get the payload using parameters(i.e Using HTTPServletRequest).So I am passing the below payload in postman.I am getting the below error.I have added the create code which I am using.

      "errordetails": [
      "code": "API_PRD_MSG/004",
      "message": "Provide atleast one description for the product.",
      "propertyref": "",
      "severity": "error",
      "target": ""

        "Product" : "BALL",
          "ProductType" : "ZHLB",
          "CrossPlantStatus" : "",
          "CrossPlantStatusValidityDate" : null,
          "CreationDate" : "\/Date(1499731200000)\/",
          "CreatedByUser" : "11279380",
          "LastChangeDate" : "\/Date(1550448000000)\/",
          "LastChangedByUser" : "HWV87616",
          "IsMarkedForDeletion" : false,
          "ProductOldID" : "",
          "GrossWeight" : "0.000",
          "PurchaseOrderQuantityUnit" : "",
          "SourceOfSupply" : "",
          "WeightUnit" : "KG",
          "NetWeight" : "0.000",
          "CountryOfOrigin" : "",
          "CompetitorID" : "",
          "ProductGroup" : "01",
          "BaseUnit" : "EA",
          "ItemCategoryGroup" : "",
          "ProductHierarchy" : "",
          "Division" : "",
          "VarblPurOrdUnitIsActive" : "",
          "VolumeUnit" : "",
          "MaterialVolume" : "0.000",
          "ANPCode" : "0",
          "Brand" : "",
          "ProcurementRule" : "",
          "ValidityStartDate" : null,
          "LowLevelCode" : "",
          "ProdNoInGenProdInPrepackProd" : "",
          "SerialIdentifierAssgmtProfile" : "",
          "SizeOrDimensionText" : "",
          "IndustryStandardName" : "",
          "ProductStandardID" : "",
          "InternationalArticleNumberCat" : "",
          "ProductIsConfigurable" : false,
          "IsBatchManagementRequired" : false,
          "ExternalProductGroup" : "",
          "CrossPlantConfigurableProduct" : "",
          "SerialNoExplicitnessLevel" : "",   
          "ManufacturerPartProfile" : "",
          "ChangeNumber" : "",
          "MaterialRevisionLevel" : "",
          "HandlingIndicator" : "",
          "WarehouseProductGroup" : "",
          "WarehouseStorageCondition" : "",
          "StandardHandlingUnitType" : "",
          "SerialNumberProfile" : "",
          "AdjustmentProfile" : "",
          "PreferredUnitOfMeasure" : "",
          "IsPilferable" : false,
          "IsRelevantForHzdsSubstances" : false,
          "QuarantinePeriod" : "0",
          "TimeUnitForQuarantinePeriod" : "",
          "QualityInspectionGroup" : "",
          "AuthorizationGroup" : "",
          "HandlingUnitType" : "",
          "HasVariableTareWeight" : false,
          "MaximumPackagingLength" : "0.000",
          "MaximumPackagingWidth" : "0.000",
          "MaximumPackagingHeight" : "0.000",
        "to_Description": {
          "results": [
              "Product" : "BALL",
              "Language" : "EN",
              "ProductDescription" : "Pipes for machines"
      	@Create(serviceName = "ProductService", entity = "Products")
      	public CreateResponse create(CreateRequest req) throws ODataException {
      		Product toCreate = new ModelMapper().map(req.getMapData(), Product.class);		
      		Product created = new DefaultProductMasterService().createProduct(toCreate)
      				.execute(new ErpConfigContext("K4XS4SDKDest"));
      		return CreateResponse.setSuccess().setData(created).response();

      Could anyone help on solving this issue?


      Thanks and Regards,

      Meenakshi A N

      Author's profile photo Alexander Duemont
      Alexander Duemont

      Dear Meenakshi,

      Please understand, the Java implementation of Cloud SDK S/4HANA VDM classes differs from your expected OData syntax. If you had checked the instance "Product toCreate" for class members, you would have found "to_Description" empty and I suppose even an error/warning must have been logged from Jackson framework, announcing unmapped entries for deserializing "to_Description". For such tasks I highly recommend creating a Unit Test first, to validate expected serialization / deserialization behavior of your custom code.

      Please try the following JSON value for "to_Description":

      "to_Description": [
          "Product" : "BALL",
          "Language" : "EN",
          "ProductDescription" : "Pipes for machines"

      Best regards