Call a BAPI/RFC Module
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.
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.
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 Neo environment, JCo is automatically provided by the infrastructure. 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
If you're starting from scratch, generate a TomEE
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()
andgetInformationMessages
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 ifgetErrorMessages()
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 ofBapiRequest
RfmRequestResult
instead ofBapiRequestResult
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
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.
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 hostnamejco.client.client
: Enter the SAP S/4HANA clientjco.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).
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
---
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.
Decoration of RFC Destinations
In the realm of HTTP, destinations allow decoration to add further features to them.
For instance, an ErpHttpDestination
can decorate an HttpDestination
which introduces the enhancement of request headers with SAP S/4HANA-specific destination properties (e.g., sap-client
).
final HttpDestination httpDestination = DestinationAccessor.getDestination(destinationName).asHttp();
final DefaultErpHttpDestination erpHttpDestination = httpDestination.decorate(DefaultErpHttpDestination::new);
For RFC destinations, such decorations do not exist.
Local Deployment
It is possible to execute BAPI calls from a Java app that runs on localhost
.
That requires some configuration steps.
Some configuration steps differ depending on which Maven archetype (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 theReadme.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
- TomEE
- Spring
- Add this new dependency to your
pom.xml
in theapplication
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
.
Replace the placeholder X.Y.Z
with the JCo version you downloaded.
- Add this new dependency to your
pom.xml
in theapplication
folder:
<dependency>
<groupId>com.sap.conn.jco</groupId>
<artifactId>com.sap.conn.jco.sapjco3</artifactId>
<version>X.Y.Z</version>
</dependency>
Replace the placeholder X.Y.Z
with the JCo version you downloaded.
- Change the packaging format to
war
file by adding:
<packaging>war</packaging>
- Use the latest version of the
maven-war-plugin
by specifying it explicitly in yourpom.xml
:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.3.1</version> <!-- use latest version -->
</plugin>
- Build the app with
mvn clean package
.
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
.
- TomEE
- Spring
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>
For Spring-based apps, we'll use a standalone Tomcat later on. For this purpose, setting the operating system-specific environment variable suffices.
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>
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 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.
- TomEE
- Spring
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>
...
You can see the system property jco.destinations.dir
via the environment variable CATALINA_OPTS
.
Example value: -Djco.destinations.dir=here-comes-the-directory-with-the-destination
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:
- Windows
- Mac
You can set the destinations
environment variable by the following command:
set destinations=[{name: "destinationName"}]
You can set the destinations' environment variable by the following command:
export destinations="[{name: \"destinationName\"}]"
Launch App on Localhost
- TomEE
- Spring
Build your app with mvn clean package
and launch it with mvn tomee:run
.
Then, access the HTTP endpoint exposed by your app.
- Build your app with
mvn clean package
. - Download Tomcat from its official page
- Extract the archive and copy it to a
tomcat
folder - Copy the war file into the
webapps
folder in thetomcat
folder - From a terminal, navigate to tomcat/bin folder and execute
catalina.bat run
(on Windows)catalina.sh run
(on Unix-based systems)
- Open
http://localhost:8080/<appname>>-application/<rest-endpoint>
to invoke 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.