Why I Wrote This
I recently wanted to build a small demo application that proves a simple point:
Can an application running on SAP BTP Kyma call an on-premise SAP system via RFC?
At first glance, this sounds like something that should be straightforward. SAP Java Connector exists, SAP Cloud Connector exists, Kyma has Connectivity Proxy, and SAP BTP has Destination service. Surely we just package sapjco3.jar, create a destination, and call STFC_CONNECTION, right?
Well — almost, but not quite.
While researching this, I found SAP Community questions such as “How can I connect my SAP BTP Kyma with my On-Premise system (ECC6 EHP7) via RFC”. The discussion captures the same uncertainty I ran into: Cloud Foundry examples exist, but Kyma is different because we build and run our own container images. There is no magic “built JCo” unless we explicitly bring the right runtime pieces (I was happy to see my own github repo referenced in the question - another indication that there is nothing out there for RFC and Kyma).
This blog documents the working path I ended up with.
Note: This is a proof of concept - SAP is not (yet) supporting this scenario. If that changes I will update the blog.
The Goal
The goal was intentionally small:
- Run a Java application in SAP BTP Kyma.
- Resolve an RFC destination from SAP BTP Destination service.
- Route RFC traffic through Kyma Connectivity Proxy.
- Reach an on-premise SAP S/4HANA system via SAP Cloud Connector.
- Execute
STFC_CONNECTION.
The successful response looked like this:
{
"ok": true,
"destination": "InsightSuiteDestination",
"function": "STFC_CONNECTION",
"jcoVersion": "3.1.13.2 (2026-04-22) for SAP Business Technology Platform",
"exports": {
"ECHOTEXT": "Hello from Kyma",
"RESPTEXT": "SAP R/3 Rel. 816 Sysid: S4H ... Logon_Data: 201/RFC_TESTUSER/E"
}
}
That was the happy end. The rest of this post is about how to get there.
High-Level Architecture
The final request flow is:
HTTP client
-> Kyma Istio route
-> Java/Tomcat app in Kyma
-> SAP JCo cloud runtime
-> SAP BTP Destination service
-> Kyma Connectivity Proxy RFC port 20001
-> SAP Cloud Connector
-> on-premise SAP system RFC gateway
-> STFC_CONNECTIONFor this demo, the relevant names were:
Kyma namespace: jco-rfc-test
RFC destination: InsightSuiteDestination
Cloud Connector ID: sap-insight-suite
Virtual RFC host: s4hana-2025japan
System number: 00
SAP client: 201
RFC function: STFC_CONNECTIONKey Lesson 1: Plain JCo Is Not Enough
My first attempt was the obvious one: package SAP JCo into a Tomcat image and run a servlet.
That was enough to start the app, but not enough to make the BTP/Kyma on-premise path work. The plain runtime did not know how to resolve BTP destinations and route RFC through the Kyma Connectivity Proxy with the required proxy authorization.
The working setup needed the SAP JCo cloud/connectivity runtime libraries that are normally provided by SAP Java Buildpack.
The important runtime ingredients were:
com.sap.conn.jco.cloud-3.1.13.2.jar
com.sap.conn.jco.cloud.rt.cloud-3.1.13.2.jar
com.sap.core.connectivity.jco-5.0.7.jar
com.sap.core.connectivity.jco.cf-5.0.7.jar
com.sap.core.connectivity.cloud.destinations.tunnel-0.9.267.jarIn my case these came from:
xs-java-buildpack-2.66.0-offline-cnb-tomcat.tgzI used the buildpack archive as a runtime overlay and copied its Tomcat libraries into my image. If you have access to the official builder, using pack build is the cleaner path.
The Minimal Java Code
The Java code itself is pleasantly small. The servlet resolves a destination and calls a function module:
JCoDestination destination = JCoDestinationManager.getDestination("InsightSuiteDestination");
JCoFunction function = destination.getRepository().getFunction("STFC_CONNECTION");
function.getImportParameterList().setValue("REQUTEXT", "Hello from Kyma");
function.execute(destination);
String echoText = function.getExportParameterList().getString("ECHOTEXT");
String responseText = function.getExportParameterList().getString("RESPTEXT");The demo app exposes this through an HTTP endpoint:
/rfc?destination=InsightSuiteDestination&text=Hello%20from%20KymaBuild Setup
The app is a WAR file running on Java 17 and Tomcat 10.1.
One important detail: SAP Java Buildpack 2.x uses the Jakarta servlet stack, so the application uses jakarta.servlet.*, not javax.servlet.*.
Relevant Maven setup:
<packaging>war</packaging>
<properties>
<maven.compiler.release>17</maven.compiler.release>
</properties>
<dependencies>
<dependency>
<groupId>com.sap.conn.jco</groupId>
<artifactId>sapjco3</artifactId>
<version>3.1.13</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/sapjco3.jar</systemPath>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
Container Image
The working image used Tomcat 10.1 and copied the SAP buildpack runtime overlay:
FROM docker.io/library/maven:3.9-eclipse-temurin-17 AS build
WORKDIR /workspace
COPY pom.xml ./
COPY lib ./lib
COPY src ./src
RUN mvn -q -DskipTests package
FROM docker.io/library/tomcat:10.1-jre17-temurin
ENV LD_LIBRARY_PATH=/usr/local/tomcat/impl/linuxx86_64:/usr/local/tomcat/lib \
JAVA_OPTS="-Djava.library.path=/usr/local/tomcat/impl/linuxx86_64:/usr/local/tomcat/lib"
COPY .tmp-buildpack/tomcat/bin/ /usr/local/tomcat/bin/
COPY .tmp-buildpack/tomcat/conf/ /usr/local/tomcat/conf/
COPY .tmp-buildpack/tomcat/lib/ /usr/local/tomcat/lib/
COPY .tmp-buildpack/tomcat/impl/ /usr/local/tomcat/impl/
COPY --from=build /workspace/target/jco-on-kyma-demo.war /usr/local/tomcat/webapps/ROOT.war
EXPOSE 8080
Before building, extract the buildpack archive:
mkdir -p .tmp-buildpack/tomcat
tar -xzf java-buildpack/xs-java-buildpack-2.66.0-offline-cnb-tomcat.tgz \
-C .tmp-buildpack/tomcatThen build and push the image to a registry Kyma can pull from.
Kyma Service Bindings
The application needs at least:
- Destination service instance and binding
- XSUAA service instance and binding
- Connectivity Proxy config and secret available in the application namespace
Example BTP service resources:
apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: destination-jco
namespace: jco-rfc-test
spec:
serviceOfferingName: destination
servicePlanName: lite
---
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: destination-jco
namespace: jco-rfc-test
spec:
serviceInstanceName: destination-jco
---
apiVersion: services.cloud.sap.com/v1
kind: ServiceInstance
metadata:
name: xsuaa-jco
namespace: jco-rfc-test
spec:
serviceOfferingName: xsuaa
servicePlanName: application
---
apiVersion: services.cloud.sap.com/v1
kind: ServiceBinding
metadata:
name: xsuaa-jco
namespace: jco-rfc-test
spec:
serviceInstanceName: xsuaa-jco
The SAP runtime expected the bindings as files. Injecting them only as environment variables was not sufficient in my setup.
- name: SERVICE_BINDING_ROOT
value: "/etc/secrets/sapbtp"
And mount the binding secrets:
volumeMounts:
- name: destination-binding
mountPath: /etc/secrets/sapbtp/destination-jco
readOnly: true
- name: xsuaa-binding
mountPath: /etc/secrets/sapbtp/xsuaa-jco
readOnly: true
volumes:
- name: destination-binding
secret:
secretName: destination-jco
- name: xsuaa-binding
secret:
secretName: xsuaa-jco
Connectivity Proxy Environment
For RFC, the app uses Connectivity Proxy port 20001:
- name: ONPREMISE_PROXY_HOST
valueFrom:
configMapKeyRef:
name: connectivity-proxy-info
key: onpremise_proxy_host
- name: ONPREMISE_PROXY_RFC_PORT
valueFrom:
configMapKeyRef:
name: connectivity-proxy-info
key: onpremise_proxy_rfc_port
- name: ONPREMISE_PROXY_TENANT_MODE
valueFrom:
configMapKeyRef:
name: connectivity-proxy-info
key: onpremise_proxy_tenant_mode
- name: CP_SECRET_CONNECTIVITY_PROXY_KYMA_SYSTEM
valueFrom:
secretKeyRef:
name: cp-secret-connectivity-proxy-kyma-system
key: service_key
In this demo the proxy endpoint was:
connectivity-proxy.kyma-system.svc.cluster.local:20001Creating the RFC Destination
This part is easy to miss.
The RFC destination is not a Kubernetes Destination custom resource. For this JCo flow, the destination must exist in SAP BTP Destination service, and JCo resolves it from there.
The working destination properties were:
Name=InsightSuiteDestination
Type=RFC
jco.client.ashost=s4hana-2025japan
jco.client.sysnr=00
jco.client.client=201
jco.client.user=RFC_TESTUSER
jco.client.passwd=<password>
jco.client.lang=EN
jco.destination.proxy_type=OnPremise
jco.client.cloud_connector_location_id=sap-insight-suite
The most important line for the Location ID was:
jco.client.cloud_connector_location_id=sap-insight-suite
Initially, I used only:
CloudConnectorLocationId=sap-insight-suite
That appeared as a raw/custom property but did not populate the RFC destination's Location ID field in SAP BTP Cockpit. The tunnel was opened as if the location ID was empty.
After adding jco.client.cloud_connector_location_id, Cockpit showed the Location ID correctly, and Connectivity Proxy opened the expected tunnel:
account:///.../sap-insight-suiteIn the repository I created a small helper script to create the destination through the Destination service API:
export RFC_PASSWORD='<password>'
./scripts/upsert-rfc-destination.py \
--namespace jco-rfc-test \
--binding-secret destination-jco \
--template destinations/InsightSuiteDestination.template.json \
--delete-first
unset RFC_PASSWORDConnectivity Proxy Authorization
Another issue I hit was proxy authorization. The Connectivity Proxy log showed:
Validation of token failed due to: Allowed client ids not set for region configuration id defaultThe fix was to configure the allowed client ID in the ConnectivityProxy custom resource:
CLIENTID=$(kubectl -n kyma-system get secret cp-secret-connectivity-proxy-kyma-system \
-o jsonpath='{.data.service_key}' \
| base64 -d \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["clientid"])')
python3 - <<PY > /tmp/cp-patch.json
import json
clientid = "$CLIENTID"
print(json.dumps({
"spec": {
"config": {
"servers": {
"proxy": {
"authorization": {
"oauth": {
"allowedClientId": clientid
}
}
}
}
}
}
}))
PY
kubectl -n kyma-system patch connectivityproxy connectivity-proxy \
--type merge \
--patch-file /tmp/cp-patch.jsonAfter this, the proxy accepted the token and opened the RFC tunnel.
How to Recognize Success
Application logs showed:
Connected to socket with {sap-insight-suite-RFC.connectivity,2}
RfcOpen(... SCCLOCATIONID=sap-insight-suite)=1
Executing function STFC_CONNECTIONConnectivity Proxy logs showed:
Will use tunnelId: account:///.../sap-insight-suite
Handshake with tunnel client completed successfullyAnd the endpoint returned:
{
"ok": true,
"exports": {
"ECHOTEXT": "Hello from Kyma"
}
}
Things I Would Watch Out For
Here are the stumbling blocks that cost the most time:
Plain JCo is not enough
You need the SAP JCo cloud/connectivity runtime, not onlysapjco3.jarandlibsapjco3.so.SAP Java Buildpack 2.x means Jakarta
If you use the 2.x runtime libraries, use Java 17 andjakarta.servlet.*.Mount service bindings as files
The runtime looked for service bindings under/etc/secrets/sapbtp.Use the RFC-specific Location ID property
For RFC destinations, usejco.client.cloud_connector_location_id.Connectivity Proxy may need allowed client ID configuration
If you seeAllowed client ids not set, patch theConnectivityProxyconfiguration.Reduce debug logging after troubleshooting
JCo/connectivity debug logs can expose destination details and token payloads.
Final Thoughts
Once all pieces were in place, the actual RFC call was simple. The hard part was not the Java code; it was assembling the correct runtime and configuration around it.
The most important takeaway for me was this:
In Kyma, RFC via JCo is possible, but you need the SAP cloud JCo/connectivity runtime and a correctly maintained BTP RFC destination.
Hopefully this saves someone else a few debugging loops — especially around the Location ID and Connectivity Proxy authorization details.
Appendix: Minimal Checklist
- [ ] Connectivity Proxy module is installed and RFC port
20001is enabled. - [ ] SAP Cloud Connector is connected to the correct subaccount.
- [ ] Cloud Connector exposes the virtual RFC host/system.
- [ ] Destination service instance and binding exist in the app namespace.
- [ ] XSUAA service instance and binding exist in the app namespace.
- [ ] Service bindings are mounted under
/etc/secrets/sapbtp. - [ ] App image includes SAP JCo cloud/connectivity runtime libraries.
- [ ] RFC destination exists in BTP Destination service.
- [ ] RFC destination has
jco.client.cloud_connector_location_idif a Location ID is used. - [ ] Connectivity Proxy authorization allows the connectivity client ID.
- [ ]
STFC_CONNECTIONworks with the configured SAP user.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.