Problem
We have a very, very old application that needs to be migrated into AWS. So we copied all files into AWS EC2 instance and tried to start the application. After fixing a lot of minor problems we faced a tough challenge with a SAPJCO RFC Call.
The Exception message was something like this:
Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC' JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [sapjcorfc (Not found in java.library.path)]. java.library.path [/usr/lib/jvm/java-1.6.0-ibm.x86_64/jre/lib/amd64/default:/usr/lib/jvm/java-1.6.0-ibm.x86_64/jre/lib/amd64:/usr/lib] at com.sap.mw.jco.JCO.<clinit>(JCO.java:871) at java.lang.J9VMInternals.initializeImpl(Native Method) at java.lang.J9VMInternals.initialize(J9VMInternals.java:199)
I guess, with a JCO Version 3 we would not have much trouble, but in this ancient application JCO Version 2 is used and we cannot update to Version 3 without a huge efford. In other projects I had the luck that I could migrate to Version.
The application is running on a Linux system. But belive me: it would have been much harder on a Windows machine.
Analysis
To find the cause of the problem I wrote the simpliest JCO Test Programm I can image:
import com.sap.mw.jco.JCO; public class TestMain { public static void main(String[] args) { System.out.println(JCO.getVersion()); } }
My analysis programm structure:
app ├─ JCo/ │ ├─ librfccm.so │ ├─ libsapjcorfc.so │ ├─ sapjco.jar ├─ TestMain.java
Compile from command line:
javac -cp ".:/app/JCo/sapjco.jar" TestMain.java
Run from command line:
java -cp ".:/app/JCo/sapjco.jar" TestMain
That gave me another error:
Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC' JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: librfccm.so: cannot open shared object file: No such file or directory]. java.library.path [/app/JCo] at com.sap.mw.jco.JCO.<clinit>(JCO.java:871) at TestMain.main(TestMain.java:11)
Need to set an environment property first:
export LD_LIBRARY_PATH=/app/JCo
Run command line to start programm again and got another error:
Exception in thread "main" java.lang.ExceptionInInitializerError: JCO.classInitialize(): Could not load middleware layer 'com.sap.mw.jco.rfc.MiddlewareRFC' JCO.nativeInit(): Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: libstdc++.so.5: cannot open shared object file: No such file or directory]. java.library.path [/app/JCo] at com.sap.mw.jco.JCO.<clinit>(JCO.java:871) at TestMain.main(TestMain.java:11)
The interesting part of the error message:
Could not initialize dynamic link library sapjcorfc [/app/JCo/libsapjcorfc.so: libstdc++.so.5
Solution
We need the libstdc++.so.5 library, but installed is libstdc++.so.6
To get libstdc++.so.5 we installed package compat-libstdc++-33-3.2.3-66.x86_64:
yum install compat-libstdc++-33-3.2.3-66.x86_64 ## to be honest, I am not exactly 100% sure, what I did in my investigations, so the command may be a little differend, ex: # yum install compat-libstdc++-33-3 # yum install compat-libstdc++-33 # yum install compat-libstdc++-33 libstdc++.so.5
Test
Run from command line:
java -cp ".:/app/JCo/sapjco.jar" TestMain
That gave me no error, but SAPJCo Version number.:
2.1.10 (2011-05-10)
Finally it worked 😎
Anekdotum
Quote from an StackOverflow post from 2010:
libstdc++.so.5 is a very old version of the standard c++ library.
Some Analysis Details
Writing this article is giving me the feeling, that this was all super easy. But in reality it was a real pain in the allerwertesten.
To isolate the source of the problem, I did not only write the small Java (JCO.getVersion) application, I also set up a Docker environment.
One challenge was to find a useful Docker image to start from. I started with an OpenJDK Image that was already deprecated. Deprecated was not the problem, but I could not install libstdc++.so.5.
Next I tried to use the newer, undeprecated Eclipse-Temurin Image. But still could not install libstdc++.so.5
So I finally ended in a Debian Image and self installed Java where I was able to install libstdc++5.
FROM debian:bookworm-slim RUN apt-get update && apt-get install -y locales openjdk-11-jdk libstdc++5 \ && rm -rf /var/lib/apt/lists/* \ && localedef -i de_DE -c -f UTF-8 -A /usr/share/locale/locale.alias de_DE.UTF-8 ENV LANG de_DE.utf8 COPY ./JCo/* /app/JCo/ COPY TestMain.java /app WORKDIR /app ENV LD_LIBRARY_PATH=/app/JCo RUN javac -cp ".:/app/JCo/sapjco.jar" TestMain.java CMD ["java", "-cp", ".:/app/JCo/sapjco.jar", "-Djava.library.path=/app/JCo", "TestMain"]
Note:
The locale might not be neccessary.
The "-Djava.library.path=/app/JCo" Parameter is not neccessary. But I leave it as an example, how one can use it.
Build and start the app:
docker build -t my-java-app . docker run -it --rm --name my-running-app my-java-app 2.1.10 (2011-05-10)
Work in container
To work in the running container:
docker exec -it my-running-app /bin/sh
But there is one problem: You can only interact with a running container. But the TestMain-Programm is executed and immediately closed.
So I wrote another Test Programm, that keeps running, so I can enter the running container and test stuff (install packages, compile Java programm, etc.):
import java.io.BufferedReader; import java.io.InputStreamReader; import com.sap.mw.jco.JCO; public class TestMain { public static void main(String[] args) { System.out.println("Hello World"); //System.out.println("JCO Version: " + JCO.getVersion()); while (true) { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.println("Enter Input : "); try { String s = br.readLine(); System.out.println(s); }catch(Exception e) { System.out.println(e); } } } }