Skip to main content

Call a BAPI/RFC Module

Warning

RFC and SOAP modules are deprecated. These modules will be kept for version 5 as well, but will be removed in the future. Please consider searching for an OData based service (e.g., via SAP Business Accelerator Hub) as possible replacement. The SAP Cloud SDK also offers OData type safe clients (both OData v2 and v4). If you have to use JCo or RFC, here is the documentation about how to consume ABAP functions via RFC, using JCo.

What Is A BAPI? What Is An RFC Module?

Per the official SAP documentation, a Business Application Programming Interface (BAPI) is a precisely defined interface providing access to processes and data in business application systems. BAPIs allow external systems to integrate with ABAP systems by reading and writing business data through the BAPI as an interface. A Remote Function Call (RFC) module can also be leveraged alongside BAPI, but the recommended approach is to use BAPIs wherever possible as BAPIs guarantee more stable and intuitive interfaces.

On the technical level, both BAPIs and RFC modules allow for invocation from external systems through the RFC protocol (Remote Function Call). As opposed to HTTP, RFC is a binary protocol highly-optimized for fast data transfer between enterprise systems.

You can find more differences between BAPI and RFC module on the programming model. Here is a brief overview:

  • BAPIs have names that always start with "BAPI". RFC modules have more flexible naming conventions.
  • BAPIs do not throw exceptions, but leverage return tables to transfer messages to the caller.
  • BAPIs do not use changing parameters, while RFC modules may do.
info

To improve readability, we use the term BAPI to refer to both BAPIs and RFC modules.

Technical Overview

SAP offers the SAP Java Connector library, hereafter referred to as JCo in short, that allows native access to BAPIs inside SAP systems. That means, one can also invoke BAPIs without using the SAP Cloud SDK by solely leveraging JCo.

SAP Cloud SDK offers a convenient Java API on top of JCo that integrates with other SAP Cloud SDK core concepts, such as the destination retrieval on SCP. It also features a convenient API to map Java entity classes to BAPI input parameters and result sets.

JCo is not packaged with SAP Cloud SDK

SAP Cloud SDK does not package JCo as a library with its dependencies. The SAP Cloud SDK assumes that JCo is available on the JVM classpath.

On the SAP BTP for the Cloud Foundry environment one must use the SAP Java Build Pack during app deployment. There are other edge cases that we'll cover later.

Call a BAPI in an SAP ABAP system

Generate a SAP Cloud SDK project

If you're starting from scratch, generate a Spring Boot project following the steps in the Getting started guide.

Implement an Example BAPI Call

Example Scenario

We build an app that we deploy to the SAP BTP, Cloud Foundry environment. This app exposes a servlet which invokes a BAPI to retrieve cost center information from e.g. SAP S/4HANA. The servlet has two input parameters which we supply in the query string:

  • destinationName: Defines which SAP BTP destination to use for connecting to SAP S/4HANA.
  • controllingArea: Defines the controlling area for the cost center retrieval.

The app retrieves the SAP BTP destination with SAP Cloud SDK core capabilities, creates a BAPI request, and issues that to SAP S/4HANA through the SAP Cloud Connector. We use the SAP Cloud Connector to access an SAP S/4HANA On-premise system.

The BAPI result contains cost center information which we print to the servlet response.

Implementation

Create Cost Center Abstraction

Firstly, we create an abstraction for the cost center as a business entity. Thereby we gain type-safe access to the cost center information.

Create a new class CostCenter and implement it as follows:

@Value
public class CostCenter {

@ElementName("CO_AREA")
String controllingArea;

@ElementName("COSTCENTER")
String id;

@ElementName("NAME")
String name;

@ElementName("DESCRIPT")
String description;
}

The annotation @Value is a class-level annotation that comes from Lombok. Check out its documentation if you are curious.

We annotate every class member with @ElementName declaring the respective ABAP field name. The SAP Cloud SDK uses this metadata to map the ABAP field to the Java field. You can look up the ABAP field name in SAP S/4HANA transaction SE37 by opening the BAPI parameter list.

To make that compile, we add Lombok to our application/pom.xml as a dependency. Open application/pom.xml and add that dependency:

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>

Run mvn clean install to verify that everything goes well.

Implement the Servlet

Open the HelloWorldSerlvet and delete its current implementation, we'll replace that with the BAPI call in the following.

Firstly, we read out the two mentioned servlet parameters from the query string.

@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response )
throws IOException
{
final String destinationName = request.getParameter("destinationName");
final String controllingArea = request.getParameter("controllingArea");
...

Thereafter, we instruct the SAP Cloud SDK to retrieve the SAP BTP destination per the provided name.

final Destination destination = DestinationAccessor.getDestination(destinationName);

Now follows the actual BAPI invocation.

final BapiRequestResult result = new BapiRequest("BAPI_COSTCENTER_GETLIST1")

We use the BAPI BAPI_COSTCENTER_GETLIST1 to fetch cost center details from SAP S/4HANA. We call it with these parameters:

  • Importing: CONTROLLINGAREA is supplied with the controlling area the servlet received
  • Tables: COSTCENTERLIST provides us a list of cost centers that belong to the controlling area along with some cost center data
  • Tables: RETURN is the BAPI return table which contains the result messages after BAPI processing

Here is how you code that with the SAP Cloud SDK.

.withExporting("CONTROLLINGAREA", "BAPI0012_GEN-CO_AREA", controllingArea)
.withTable("COSTCENTERLIST", "BAPI0012_CCLIST").end()
.withTableAsReturn("BAPIRET2")

We call withExporting with the parameter name as the first argument. The second argument BAPI0012_GEN-CO_AREA represents the ABAP data type of the BAPI parameter. You can look that up in transaction SE37 under the respective BAPI name. Thirdly, we pass the actual controlling area to the method.

Using withTable allows us to access the table parameter result after BAPI processing. If we would not call this method, the SAP Cloud SDK would ignore this tables parameter from the BAPI response, even though it contains some data. Again, the second parameter is the ABAP data type.

The invocation of method withTableAsReturn instructs the SAP Cloud SDK to treat the tables' parameter with the name BAPIRET2 as BAPI result table. We'll see later how that helps us access the BAPI result in Java.

The BAPI invocation gets finished with a call to .execute(destination).

Here is the full method call chain:

final BapiRequestResult result = new BapiRequest("BAPI_COSTCENTER_GETLIST1")
.withExporting("CONTROLLINGAREA", "BAPI0012_GEN-CO_AREA", controllingArea)
.withTable("COSTCENTERLIST", "BAPI0012_CCLIST").end()
.withTableAsReturn("BAPIRET2")
.execute(destination);

We receive an instance of BapiRequestResult. Let's look at how to access the BAPI result in detail.

As mentioned above, we're interested in the cost center list and its content. Therefore, we access the COSTCENTERLIST tables parameter.

final List<CostCenter> costCenterList = result
.get("COSTCENTERLIST")
.getAsCollection()
.asList(CostCenter.class);

This helps you automatically retrieve List<CostCenter> and can access the output in a type-safe way. On the return type of result.get("COSTCENTERLIST") you call getAsCollection() to treat this parameter as a collection. And then you instruct the SAP Cloud SDK to transfer the contained entries into individual CostCenter instances with asList(...).

Further processing depends on the use case. For example, we could print the cost centers to the servlet response.

for (final CostCenter costCenter : costCenterList) {
response.getWriter().write(costCenter.toString());
}

Before deploying the app, let's point out more possibilities to access the BAPI result. The class BapiRequestResult allows you to access the return table as follows:

  • Use getErrorMessages(), getSuccessMessages(), getWarningMessages() and getInformationMessages to access the returned messages.
  • Use hasFailed() to figure out if the BAPI call was in itself working. Note that this is a convenient method for checking if getErrorMessages() is empty. The interpretation if the BAPI call was successful, highly depends on the business context and you could implement custom logic for that purpose.

You might notice that the execute method declares to throw a RequestExecutionException in case of a failure at runtime. That requires encapsulating the BAPI call in a try-catch block.

try {
final BapiRequestResult result = new BapiRequest("BAPI_COSTCENTER_GETLIST1")
.withExporting("CONTROLLINGAREA", "BAPI0012_GEN-CO_AREA", controllingArea)
.withTable("COSTCENTERLIST", "BAPI0012_CCLIST").end()
.withTableAsReturn("BAPIRET2")
.execute(destination);

final List<CostCenter> costCenterList = result
.get("COSTCENTERLIST")
.getAsCollection()
.asList(CostCenter.class);

for (final CostCenter costCenter : costCenterList) {
response.getWriter().write(costCenter.toString());
}
} catch (final RequestExecutionException e) {
e.printStackTrace(response.getWriter());
}

Run mvn clean install to verify that your app compiles and to assemble it to a deployable artifact.

Difference for RFC Module

The presented code works for RFC modules too. The difference is to use alternative classes:

  • RfmRequest instead of BapiRequest
  • RfmRequestResult instead of BapiRequestResult

Configure & Deploy to a Cloud Foundry Environment

Prerequisites

Here is what you'll need to make progress throughout the next steps.

  • CF CLI installed on your machine
  • User in SAP S/4HANA target system
  • SAP BTP subaccount for the Cloud Foundry environment with entitlements for XSUAA, Destination and Connectivity service
  • SAP Cloud Connector connected to the subaccount (if SAP S/4HANA is On-premise)
  • App Router (for some use cases, see note below)

Official tutorials for setting up these scenarios can be found here.

Set Up App Router

Relevant Use Cases

An App Router is required if any of these conditions are met:

  • The app is multi tenant
  • Principal Propagation is used
  • If SAP S/4HANA is On-premise and the subaccount ID differs from the tenant ID of your CF subaccount

For the above use cases to function it is required that the app is invoked with a JWT containing the user context. For that, securing the backend with an app router is required. Follow this tutorial to set up the app router.

Multitenancy

In case your application should be multi tenant it is also required to mitigate scope conflicts with the SAP Java Buildpack.

Setup Cloud Connector for SAP S/4HANA On-Premise access

Expose the SAP S/4HANA system in the SAP Cloud Connector as described in the official documentation.

Setup Destination

Open the SAP BTP cockpit on the subaccount level. Navigate to Connectivity -> Destinations and create a new RFC destination with these properties.

  • Name: BAPI-TEST (you can also choose your own name)
  • Type: RFC
  • ProxyType: OnPremise
  • User: Enter the SAP S/4HANA username
  • Password: Enter the SAP S/4HANA password
  • Additional properties:
    • jco.client.ashost: Enter the SAP S/4HANA hostname
    • jco.client.client: Enter the SAP S/4HANA client
    • jco.client.sysnr: Enter the SAP S/4HANA instance number

For performance improvements of your app, please consider using optional properties such as jco.client.serialization_format (see further in JCo documentation).

info

This destination setup is exemplary for accessing an On-premise SAP S/4HANA system directly. If you want to access it e.g. via message server logon or you want to access a cloud system via WebSocketRFC, please adapt the properties as described in the official documentation.

Deploy to the SAP Business Technology Platform, Cloud Foundry Environment

Invoke cf push from the directory root to deploy the app to your Cloud Foundry space.

Call the Test App

Our test app has the two servlet parameters destinationName and controllingArea. Suppose our destination has the name BAPI-TEST and we intend to read cost centers from controlling area 0001, we'd call the test app with the relative path hello?destinationName=BAPI-TEST&controllingArea=0001.

Access the test app through your browser with the address consisting of the full hostname plus the relative path. You should see the login prompt of the protected backend. After authentication, the servlet is invoked and you should see the servlet response printing the cost center details.

Advanced BAPI Calls

Use of API for Table Parameters of a BapiRequest

When a table uses a domain-type as row-type there are no field names to refer to. But such a table can be treated as a one-dimensional vector of primitive values, e.g.:

new BapiRequest("BAPI_DEMO", true)
.withExportingTable("DEMO_TABLE", "BAPI_DEMO_TAB01")
.asVector()
.row("CC01")
.row("CC02")
.row("CC03")
.end();

Recursive Tables and Structures in Parameters of a BapiRequest

The following example demonstrates the API for recursive tables and structures in parameters of BapiRequest for both protocols: JCo and SOAP.

final BapiRequest bapiReqauest = new BapiRequest("DEMO_BAPI")
.withExportingTable("TABLE_PARAMETER1","ABC_T_PARAM1" )
.row()
.field("ROW_UUID","STRING","foobar")
//Start nested table
+ .table("TABLE_NESTED1","ABC_T_NESTED1")
+ .row()
+ .field("NESTED_FIELD1","STRING","john")
+ .field("NESTED_FIELD2","STRING","doe")
+ .end() // End nested table
// START nested structure
.fields("NESTED_STRUCTURE", "NESTED_STRUCTURE_TYPE")
.field("latitude", "Axis", "52")
.field("longitude", "Axis", "13")
.end() // End nested structure
.end();

Clearing the Java Connector Cache

The Java Connector library caches metadata about the BAPI interface to gain performance. In case the BAPI interface changes in the backend during the application runtime, the metadata in the cache and in the backend can become inconsistent. This inconsistency can lead to severe errors and difficult to understand exceptions. For such cases, the metadata cache can be cleared explicitly on different levels of granularity.

You can clear the Java Connector cache per destination with the following Java API.

RemoteFunctionCache.clearCache(destination);

This clears the cache for all BAPIs called over this destination. Upon invoking a BAPI over this destination with the cleared cache, the Java Connector will retrieve the metadata again and the consistency is recovered.

You can also clear the cache for a Destination and a certain BAPI.

RemoteFunctionCache.clearCache(destination, "BAPI_COSTCENTER_CREATEMULTIPLE");

This call clears the metadata cache of the selected BAPI for the given Destination.

Boundary Conditions and Edge Cases

JCo Classes Not Found at Runtime

The JCo classes are available on the JVM classpath at runtime. If not, the exception in such cases reads like java.lang.NoClassDefFoundError: com/sap/conn/jco/JCoException.

Possible reasons and resolutions:

  • There have been issues with finding JCo classes in a Spring Boot project. In such cases using the "traditional deployment" of Spring solved the problem. Thereby you deploy a war file instead of a jar file and you use Tomcat as the servlet container. Refer to the guide on the traditional Spring deployment to overcome this issue.
  • On SAP BTP, Cloud Foundry environment, the SAP Java Build Pack brings JCo as a dependency. The problem arises when the build pack is not used. That is, you must declare its usage in your deployment descriptor manifest.yml as such:
- name: testapp
---
buildpacks:
- sap_java_buildpack_jakarta
---
env:
USE_JCO: true
  • Furthermore, you must specify the environment variable USE_JCO to load JCo during app deployment.

Dependency Conflicts at Runtime

If you see exceptions related to classes or methods not being found this can indicate a dependency conflict. Please visit the Notes on the SAP Java Buildpack and follow the guide on mitigating conflicts.

Subscriber Tenant Is Not Found at Runtime

In case you want to use multitenancy or multithreading the recommendations to avoid scope conflicts become mandatory.

Furthermore, please check out the documentation here, especially if you want to call JCo from any newly created thread.

Local Deployment

It is possible to execute BAPI calls from a Java app that runs on localhost. That requires some configuration steps.

note

Some configuration steps differ depending on which framework (Spring or TomEE) you use.

Download and Install JCo Library

  • Download the JCo library from the official product page.
  • Extract the downloaded archive and note the JCo version (e.g., 3.1.2) from the Readme.txt file.
  • Install the JAR file to your local Maven cache by running the following command:
    tip

    Before running the command, replace <sapjco-version> with the JCo version you downloaded.

    mvn install:install-file -Dfile=sapjco3.jar -DgroupId=com.sap.conn.jco -DartifactId=com.sap.conn.jco.sapjco3 -Dversion=<sapjco-version> -Dpackaging=jar
  • Now Maven uses the JCo JAR file when declaring the dependency in your pom file.

Add a Dependency To Your pom.xml

  • Add this new dependency to your pom.xml in the application folder:
<dependency>
<groupId>com.sap.conn.jco</groupId>
<artifactId>com.sap.conn.jco.sapjco3</artifactId>
<version>X.Y.Z</version>
</dependency>
  • Build the app with mvn clean package.
tip

Replace the placeholder X.Y.Z with the JCo version you downloaded.

Add JCo Library To System Path

Open the file javadoc/installation.html from the extracted JCo archive and follow the installation instructions. Essentially, you have to add the directory where you extracted the JCo archive to one system environment variable. The variable name depends on your operating system.

  • For Windows, add the directory which contains the downloaded JCo archive to the system environment variable PATH.
  • For macOS, add the directory which contains the downloaded JCo archive to the system environment variable DYLD_LIBRARY_PATH.

If you face an exception like the following ones, then you must configure the classpath to contain the sapjco3.jar file to as shown hereafter.

  • Could not initialize class com.sap.conn.jco.JCo
  • java.lang.UnsatisfiedLinkError: no sapjco3 in java.library.path

To overcome these exceptions, configure the classpath via the TomEE Maven plugin.

<plugin>
<groupId>org.apache.tomee.maven</groupId>
<artifactId>tomee-maven-plugin</artifactId>
<configuration>
...
<classpaths>here-comes-the-path-to/sapjco3.jar</classpaths>
...
</configuration>
</plugin>

Create a Destination File

  • Create a file <destination-name>.jcoDestination where you replace <destination-name> with the name of the destination.
  • Add the following properties to the file:
jco.client.ashost=<hostname>
jco.client.client=<sap-client>
jco.client.sysnr=<instance-number>
jco.client.user=<user>
jco.client.passwd=<password>
tip

Replace every <placeholder> with the respective value.

Point JCo To Your Destination File

JCo considers the system property jco.destinations.dir to look for destination files. The property value must be the directory where the destination file resides.

note

Note that the system property must point to the directory, not to the Destination file.

You can set the property in your Java code like so:

System.setProperty("jco.destinations.dir", "here-comes-the-directory-with-destination");

Alternatively, you can pass the system property to the JVM via configuration in different ways depending on the chosen archetype.

You can see the system property jco.destinations.dir via the Maven plugin configuration as follows:

<plugin>
<groupId>org.apache.tomee.maven</groupId>
<artifactId>tomee-maven-plugin</artifactId>
<configuration>
<args>-Djco.destinations.dir=here-comes-the-directory-with-the-destination</args>
...

Set Destination as Environment Variable

To let your destination be found in the local environment you need to supply an environment variable destinations on your local machine:

You can set the destinations environment variable by the following command:

$env:destinations='[{"name": "destinationName", "type": "RFC"}]'

Launch App on Localhost

Build your app with mvn clean package and launch it with mvn tomee:run. Then, access the HTTP endpoint exposed by your app.

WebSocket RFC Client Communication

JCo supports RFC calls from the client-side via web sockets as of version 3.1.2. For this scenario, a certain configuration is required within the SAP ERP system as well as new RFC destination properties must be configured. Download the JCo package from JCo product website and check out the contained release notes for further details.