Other parts of this material show how to expose ILE programs through industry standard methods such as XML or JSON web services, SSH, and TCP/IP sockets. Java applications work comfortably with those protocols. Beyond the standard approaches there are several direct ways for Java to call or work with ILE code. This section walks through the most common and practical techniques.
The IBM Toolbox for Java (part of the JTOpen family) is the most popular route for calling ILE from Java. It gives you a solid set of classes that let a Java program reach almost anything on IBM i.
With the toolbox you can:
- Run CL commands
- Call programs and service programs
- Access DB2 for i through JDBC or record level access
- Work with IFS files, spooled files, data queues, environment variables, system values, users, jobs, user spaces and more
Because everything runs over TCP/IP, the same code works for remote or multi tier solutions. Many IBM products are built on top of the toolbox.
You can download JTOpen | Overview but it is also shipped free with the JC1 licensed program. The JAR files land in /QIBM/ProdData/HTTP/Public/jt400/lib/. The two files you care about most are:
- jt400.jar – the standard cross platform APIs
- jt400Native.jar – the same APIs with extra optimisations when the Java code runs on IBM i itself
A quick start with the toolbox
Almost everything begins with an AS400 connection object.
as400.setSystemName("mySystem");
as400.setUserId("myUID");
as400.setPassword("myPWD");
// or the shorter form
AS400 as400_2 = new AS400("mySystem", "myUID", "myPWD");
Once you have the connection you can create the other objects you need.
Running a simple command
try {
boolean isSuccessful = cc.run("CRTLIB FRED");
// check isSuccessful
for (AS400Message msg : cc.getMessageList()) {
// handle messages
}
} catch (Exception e) {
log(e);
}
Calling a service program with ServiceProgramCall
The ServiceProgramCall class lets you invoke procedures inside an ILE service program. You must know the parameter types and whether they are passed by value or by reference.
Here is a complete example that passes two zoned decimal values by reference:
// Input parameter
AS400ZonedDecimal tempVar = new AS400ZonedDecimal(10, 2);
// Output parameter
AS400ZonedDecimal outVar = new AS400ZonedDecimal(10, 2);
// "inParam" is a BigDecimal passed into this method
parmList[0] = new ProgramParameter(
ProgramParameter.PASS_BY_REFERENCE,
tempVar.toBytes(inParam));
parmList[1] = new ProgramParameter(
ProgramParameter.PASS_BY_REFERENCE, 10);
ServiceProgramCall svcpgm = new ServiceProgramCall(conn);
svcpgm.setProgram(
"/QSYS.lib/FLGHT400C.lib/SMPLAPISVC.srvpgm", parmList);
svcpgm.setProcedureName("CONVERTTEMP");
if (svcpgm.run() == false) {
AS400Message[] msgs = svcpgm.getMessageList();
for (int i = 0; i < msgs.length; ++i) {
System.out.println(msgs[i].getID() + ": " + msgs[i].getText());
}
} else {
byte[] outData = parmList[1].getOutputData();
BigDecimal outParam = (BigDecimal) outVar.toObject(outData);
// use outParam
}
The toolbox supplies a family of helper classes that map Java types to IBM i data types. The most common ones are:
| Java data type | IBM i data type |
|---|---|
| Object[] | Array |
| short | 2 byte binary |
| int | 4 byte binary |
| long | 8 byte binary |
| byte[] | Byte array |
| float | 4 byte floating point |
| double | 8 byte floating point |
| BigDecimal | Packed decimal |
| BigDecimal | Zoned decimal |
| Object[] | Structure |
| String | Text |
The toolbox takes care of code page conversion, byte order and the actual data movement for you.
Using PCML to simplify the call
Instead of coding every data type in Java you can let the compiler generate PCML (an XML description of the parameters) when you build the ILE service program. The toolbox then reads that description.
Example PCML:
<program name="CONVERTTEMP" entrypoint="CONVERTTEMP"
path="/QSYS.lib/FLGHT400C.lib/SMPLAPISVC.srvpgm">
<data name="TEMPIN" type="zoned" length="10" precision="2" usage="input" />
<data name="TEMPOUT" type="zoned" length="10" precision="2" usage="output" />
</program>
</pcml>
Calling it becomes much cleaner:
new ProgramCallDocument(conn, "com.ibm.sample.pcall.simpleapi");
pcmlDoc.setValue("CONVERTTEMP.TEMPIN", inParam);
boolean result = pcmlDoc.callProgram("CONVERTTEMP");
if (!result) {
AS400Message[] msgs = pcmlDoc.getMessageList("CONVERTTEMP");
// handle errors
} else {
BigDecimal outParam =
(BigDecimal) pcmlDoc.getValue("CONVERTTEMP.TEMPOUT");
// use outParam
}
If the service program signature changes you only update the PCML. The Java code stays the same.
*Calling a regular PGM with ProgramCall
ProgramCall works almost exactly like ServiceProgramCall. The only difference is that it targets a program object instead of a service program procedure. The same parameter classes and PCML support apply.
Calling SQL stored procedures through JDBC
Another clean route is to wrap the ILE logic in an SQL stored procedure and call it with ordinary JDBC. This approach has a few advantages:
- You can pass up to 1 024 parameters (Toolbox ProgramCall is limited to 35, ServiceProgramCall to 7)
- The procedure can return a result set of any size
- You stay inside the familiar JDBC world
You do need to know how to write and register the stored procedure, but once that is done the Java side is straightforward.
Using Data Queues
Data queues remain a solid, language neutral way for programs to talk to each other. The toolbox gives full support for both sequential and keyed data queues, LIFO or FIFO ordering, and non destructive peeks.
Example that writes a simple customer record:
BinaryFieldDescription custNumberField =
new BinaryFieldDescription(new AS400Bin4(), "CUSTOMER_NUMBER");
CharacterFieldDescription custNameField =
new CharacterFieldDescription(new AS400Text(50, conn), "CUSTOMER_NAME");
RecordFormat sampleRecord = new RecordFormat();
sampleRecord.addFieldDescription(custNumberField);
sampleRecord.addFieldDescription(custNameField);
DataQueue dq = new DataQueue(conn,
"/QSYS.LIB/FLGHT400C.LIB/CUSTINFO.DTAQ");
try {
dq.create(96); // max entry size 96 bytes
} catch (ObjectAlreadyExistsException e) {
// already exists – fine
}
Record data = new Record(sampleRecord);
data.setField("CUSTOMER_NUMBER", Integer.valueOf(customerNumber));
data.setField("CUSTOMER_NAME", customerName);
byte[] byteData = data.getContents();
dq.write(byteData);
JTOpenLite
JTOpen also ships a much smaller package called JTOpenLite (jtopenlite.jar). It offers a lighter set of APIs for programs, service programs, IFS, commands, a lightweight JDBC driver and record level access.
Reasons you might choose it:
- Smaller JAR and smaller memory footprint
- Some operations run faster
- It works well for native Android applications
Command call example with JTOpenLite:
CommandConnection.getConnection("mySystem", "myUID", "myPWD");
CommandResult result = cc.execute("CRTLIB FRED");
boolean isSuccessful = result.succeeded();
for (Message msg : result.getMessages()) {
// handle messages
}
Java Native Interface (JNI)
JNI is the most powerful (and most complex) integration path. You write native methods in ILE RPG or C that can be called directly from Java, and from the ILE side you can reach back into the running JVM, create Java objects, call Java methods and manipulate arrays. It is true two way integration, but it requires more care than the Toolbox approaches.
Runtime.exec()
Finally, plain Java gives you Runtime.exec(). Because Java on IBM i runs in PASE you can use the PASE system utility to fire CL commands or CALL a program:
"system \"CALL PGM(MYLIB/MYPROGRAM)\"");
OutputStream stdin = p.getOutputStream();
InputStream stdout = p.getInputStream();
InputStream stderr = p.getErrorStream();
This is the quickest method when you only need to launch something and do not need rich parameter handling. It only works when the Java code itself is running on IBM i.
Those are the main practical routes from Java into ILE. Most shops start with the IBM Toolbox for Java because it is mature, well documented and covers almost every common requirement.
