🛠️ P3/COBOL 25.1.0-SNAPSHOT is available for preview.
User Guide
Mixed-language Use

Calling Java from P3/COBOL

Calling Java from a P3/COBOL program can be done in several different ways, depending on the application architecture and personal preferences of the COBOL developer. The easiest is one which uses COBOL language extensions unique to P3/COBOL, specifically by using Java code embedded directly in your COBOL application paragraph(s). To use this approach you include code similar to the following at the point in your program where you wish to instantiate a Java class and/or interact with a Java class:

Embedded Java

java
    MyJavaClass myJavaClass = new MyJavaClass();
    `set MY-COBOL-RETURN-CODE to` myJavaClass.aMethod(`MY-COBOL-VAR as string`, `MY-COBOL-NUMBER`) `end-set`;
end-java

As you can see, this approach is very straightforward. The only things you need to remember are:

  1. Any references to Java packages not in the same path as the COBOL program must explicitly use the full class name or be included in a REPOSITORY paragraph in the COBOL program’s CONFIGURATION SECTION.
  2. This approach uses the COBOL-embedded-in-Java feature of P3/COBOL (“embedded COBOL”) described below.
  3. The life cycle of a COBOL program is from the time it is first called until either the controlling Run Unit is terminated or the program is cancelled by another program. This means that any objects instantiated within embedded Java will not be garbage collected until either all references are eliminated or one of the two preceding events occur. Returning from the calling program to a caller will not automatically do this.

Embedded COBOL (in embedded Java)

Embedded Java code must use generated getters and setters to access COBOL program data items from within embedded Java. Doing this directly can be tedious and fragile, as the getter names will likely change if the COBOL program changes. This is a better way to access COBOL data from within embedded Java (known informally as "tic" notation).

P3/COBOL has a unique feature that allows the COBOL data items that are in-scope at the point of the JAVA statement to be accessed using standard COBOL syntax (i.e., qualified identifier references including subscripts and reference modification). This is accomplished using the accent ` character anywhere within the JAVA ... END-JAVA block to signal a temporary transition back into COBOL syntax, where special COBOL GET and SET statements provide the means to retrieve or set the value of COBOL data items as shown above. The Java type of the item values being gotten or set is that which is generated for the item's accessors based on the COBOL usage and picture unless forced to a Java String or CobolPointer by the AS STRING or ADDRESS OF modifiers respectively.

The general syntax for the embedded COBOL is:

[GET] [ADDRESS OF] cobolIdentifier [AS STRING]\bold\lq \lbrack \bold{GET} \rbrack \space \lbrack \bold{ADDRESS} \space \bold{OF} \rbrack \space \textit{cobolIdentifier} \space \lbrack \bold{AS} \space \bold{STRING} \rbrack \bold\lq

in order to get the natural value of a COBOL data item and,

SET cobolIdentifier [TO] javaIdentifier ENDSET\bold\lq \bold{SET} \space \textit{cobolIdentifier} \space \lbrack \bold{TO} \rbrack \bold\lq \space \textit{javaIdentifier} \space \bold\lq \bold{END} \text{--} \bold{SET} \bold\lq

in order to set the value of a COBOL data item.

This can be nested to any level desired if subscript or reference modification values require transition between COBOL and Java code. For example:

SET cobolIdentifier [TO] myJavaArray[myCobolIndex – 1] ENDSET\bold\lq \bold{SET} \space \textit{cobolIdentifier} \space \lbrack \bold{TO} \rbrack \bold\lq \space \textit{myJavaArray} \lbrack \lq \small{myCobolIndex} \lq \space \text{--} \space \small{1} \rbrack \space \bold\lq \bold{END} \text{--} \bold{SET} \bold\lq

⚠️

COBOL uses 1-origin indexing, even when embedded in Java. In other words, whether the index value is expressed in COBOL or in Java, if it applies to a COBOL data item reference, the first element is addressed with a subscript of 1.

REPOSITORY Clause

In Java source files, references to externally defined classes may be declared using the import statement at the beginning of the source file. In COBOL programs using the embedded Java feature, an equivalent capability is provided by the REPOSITORY paragraph in the CONFIGURATION SECTION of the program.

The structure of the repository paragraph when used to import Java classes is as follows:

[{CLASS JAVA javaImportPath ENDJAVA}]\lbrack \lbrace \bold{CLASS} \space \bold{JAVA} \space \textit{javaImportPath} \space \bold{END} \text{--} \bold{JAVA} \rbrace \dotsb \rbrack

Where the javaImportPath is any valid Java import path specification. Unlike statement-level JAVA ... END-JAVA blocks, this repository form is an ordinary COBOL clause: CLASS, JAVA, the import path, and END-JAVA may appear on a single line. This repository paragraph must be in the first (outermost) program if nested programs are used, even when the Java that uses it is in a nested program. For more information as to how this import path affects references in the Java code, see the Java language documentation for the Java compiler you are using to compile the P3/COBOL generated classes.

COBOL Natural Types

All elementary COBOL-defined items (variables) GET/SET to one of the following standard Java types as their "natural" Java type:

Java typesCobol Types
ByteBufferAny group, alphanumeric, alphabetic, or edited data item
StringAny national or national-edited data item
intAny elementary numeric item with a (scaled) precision of 9 or fewer digits and having no fractional part
longAny elementary numeric item with a (scaled) precision of 10 to 18 or fewer digits and having no fractional part
BigIntegerAny elementary numeric item with a precision of 19 to 63 digits and having no fractional part
int[]
BigDecimal
CheckedDecimal
Any non-floating point elementary numeric item with a decimal fractional part
floatAny single-precision (comp-1 except RM dialect) floating point item
doubleAny double-precision (comp-2) floating point item
byte[]Any stored data pointer item

Within the generated program, all data manipulations are done in these basic Java types. When the items are set, they are converted to the proper persistent (storage) form based on the configuration of the compiler. That "stored form" is normally not of any concern to the COBOL programmer or the embedded Java programmer, as all storage and retrieval should take place through accessors (preferably using the "tick" notation) that will utilize the natural Java type.

For some cases of accessing COBOL items in other than their natural form as above or for obtaining pointers to stored COBOL items, additional getters and setters are generated. Their use requires a more advanced understanding of the nature of COBOL operations on data items.

The use of the ADDRESS OF modifier for the GET verb causes a CobolPointer type Java value to be returned. That CobolPointer may then be dereferenced and one of the above natural-type values obtained by the using methods provided by the DataUtil utility class and/or instance of that class.

Using Generated Accessors Directly

While the preceding embedded Java approach is preferred, you may also use the generated data accessors directly.

⚠️

This is an advanced technique and should be attempted only if you feel that you understand the Java generated by P3/COBOL. If you are not sure, the previous methods should be employed and this section may be skipped.

In Java, all COBOL user item names are mapped ("munged") to Java-friendly equivalents by replacing all - characters with _, lower-casing all alphabetic characters, and preceding a leading digit with an _ character. The first letter position is lower-case when the item is referenced as a group object instance, and capitalized elsewhere. If there is more than one name that is generated in the same program context, the second and subsequent ones have an ascending number appended following an additional _ character.

The convention used for using the generated Java COBOL item getters and setters directly (without using embedded-COBOL as above) is as follows:

For getting and setting the native (Java) value of the COBOL item:

[groupName.] get itemName ([index][ , dependencyIndex ])\lbrack \textit{groupName} \text{.} \rbrack \space \texttt{get} \space \textit{itemName} \space \lparen \lbrack \small\text{index} \dots \rbrack \lbrack \space \text{,} \space \textit{dependencyIndex} \space \rbrack \rparen

[groupName.] set itemName (value [, index][ , dependencyIndex ])\lbrack \textit{groupName} \text{.} \rbrack \space \texttt{set} \space \textit{itemName} \space \lparen \textit{value} \space \lbrack \small\text{, index} \dots \rbrack \lbrack \space \text{,} \space \textit{dependencyIndex} \space \rbrack \rparen

Where:

IdentifierDescription
groupNameis the generated name of the top-level group containing this item;
itemNameis the generated name of the item accessed;
index ...is a comma-separated list of indexes if the item is subscripted;
dependencyIndexis the dependency item value for the last OCCURS DEPENDING ON at or below the level of the item accessed, if any;
valueis a congruent Java-type value for the item to be set.

If you have any question about the getters or setters for your particular program, examine the generated Java class(es) to see what is available.

Optionally, Java Bean accessors can be generated for 01-level group items. In that case all accessors use and return common Java types. For additional information regarding the access of COBOL values via Java Bean methods, see generate-java-beans.

CALLing Java as COBOL

Another way to call Java from COBOL is to provide, on the Java side of the CALLs, a Java class that is functionally a well-formed COBOL subprogram. This would be the best use case when previously COBOL language programs are remodeled as Java, but are still required to perform identically within the overall COBOL application architecture.

This is slightly more complex to accomplish than the COBOL-embedded-Java approach, but it enables the use of standard COBOL using no dialect-specific extensions on the CALLing side.

In the COBOL application a normal COBOL CALL may be used in this manner to execute external Java code that is constructed to support direct COBOL calling. For example:

call "MyJavaProgram" using my-cobol-var returning my-other-cobol-var.

The COBOL-callable Java program being invoked above would look something like this:

package programs;
 
import com.turrettech.p3cobol.common.*;
import com.turrettech.p3cobol.lib.*;
import com.turrettech.p3cobol.lib.Program.CallableProgram;
 
public class MyJavaProgram extends CallableProgram {
 
    @Override
    public Object call(Object ... args) {
        // do whatever you want in Java
        long meaningOfLife = DataUtil.getLong(args[1]);
 
        // do some Java work (potentially including calling COBOL programs)
 
        // return to CALLer (any uncaught exceptions will be handled by the CALL logic by default)
        return Long.valueOf(meaningOfLife);
    }
    // other Java methods supporting whatever this is supposed to do
}

As you see, the callable Java object must extend the CallableProgram class from the P3/COBOL library. By so doing, it will be manageable by the Run Unit controlling the P3/COBOL program, and need only support the call(Object... args) method. In the example, the program receives an arbitrary number of calling arguments (depending on how many are specified in the CALL), and returns the integer value of the first argument (based on the RETURNING item supplied in the CALL).

For arguments passed BY REFERENCE or BY CONTENT, the argument Object... consists of the returning argument pointer (a CobolPointer Java object) as the 0th element, and the actual argument list as the 1st through nth remaining argument Objects comprising CobolPointer objects referencing the calling arguments and their content. When called BY VALUE, the argument list will contain the argument as a natural type as described previously.

To return a value to the calling COBOL program, you must set the value using the CobolPointer passed as args[0] in this example. There are convenience methods in the utility class DataUtil that simplify the process of getting and setting arguments and return values from called Java.

In particular:

Getter/SetterDescription
getBytes(Object o)Always works; gets byte[] of stored form
getByteBuffer(Object o)Always works; gets ByteBuffer of stored form
getString(Object o)Always works; gets the equivalent String value of the stored form (converts numeric arguments according to Java conventions)
getInt(Object o)Returns either the Integer value or a ByteBuffer if called BY CONTENT
getLong(Object o)Returns either the Long value or a ByteBuffer if called BY CONTENT
getDouble(Object o)Returns either the Double value or a ByteBuffer if called BY CONTENT
getBigInteger(Object o)Returns either the BigInteger value or a ByteBuffer if called BY CONTENT
getBigDecimal(Object o)Returns either the int[] value or a ByteBuffer if called BY CONTENT
set(CobolPointer p, byte[] v)Always works; sets byte[] stored form
set(CobolPointer p, String v)Always works; converts to numeric form if appropriate
set(CobolPointer p, long v)Works for numeric types
set(CobolPointer p, double v)Works for numeric types
set(CobolPointer p, BigInteger v)Works for numeric types
set(CobolPointer p, int[] v)Works for numeric types

Any of the utility methods may throw a P3DataException unchecked exception if the requested action cannot be carried out.

⚠️

These methods do not detect loss of significance or truncation. If the argument is of unknown size, you should use a receiving type that can contain it regardless of size. This exception may be caught and swallowed if this is not a concern for the Java program.

Calling COBOL Programs From Java

Calling COBOL from Java is almost as simple as calling Java from COBOL. Every P3/COBOL program runs under the control of a RunUnit object instance. This run unit manages external files and data items, calling and called programs and the program stack, cancelling programs and releasing resources, and communication with any debugging client, among other things. Each run unit may be associated with any number of COBOL execution threads, but only one thread may be actively executing COBOL code at a given time unless special provision is made. For multi-threaded COBOL use, a separate RunUnit instance should usually exist for each independent thread.

JVM Options Required by P3/COBOL

P3/COBOL 25.0 requires Java 25 or later and the Java Foreign Function and Memory API (FFM). Any JVM that starts the P3/COBOL runtime, including a Java application that invokes COBOL later through the runtime API, must be launched with the options below.

When launching from Eclipse, the P3/COBOL IDE can determine the selected JRE version and add any missing options without duplicating options already present in the launch configuration. See Configuring JVM Access Options in Eclipse.

Applications launched outside Eclipse must supply the following options.

Java 25 and Later

--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--enable-native-access=ALL-UNNAMED

The former sun.misc.Unsafe compatibility path and the PREFER_FOREIGN_MEMORY=false system property are not supported. JVM option sets previously documented for Java 17 and Java 21 apply only to older P3/COBOL lines; see the 17.1 release notes and 17.0 release notes for historical detail.

Calling COBOL Synchronously On the Current Thread

In this case the only consideration is the context within which the Java is running, either as a POJO object, or as Java that was directly or indirectly called by COBOL.

Calling COBOL from Java Embedded In or CALLed From COBOL

Assuming the current thread already has an active RunUnit attached and the current Java code is running with a COBOL context established, the

COBOL program call need only invoke a static method on the RunUnit class. For example:

// calling the "my-cobol" program with one argument and a return value
long myAnswer = 0;
 
try {
    // the next line calls the "my-cobol" program on this thread
    CobolPointer resultPointer = RunUnit.call("my-cobol", null, 42);
 
    // the call returned a CobolPointer instance; dereference the returned pointer to get its value
    myAnswer = dataUtil.getLong(resultPointer);
 
    // now we will print the returned answer
    System.out.println("MyCaller's answer is: " + myAnswer);
 
} catch (P3RuntimeException p3e) {
    // any fatal error in the preceding will throw an exception; we dutifully catch it here
    System.err.println("something went wrong");
}

Of course the try/catch usage may be modified for the circumstances of the Java that is running. The important thing is the RunUnit.call(...) mechanism.

Calling COBOL to Run On a Different Thread

The other case is when you wish to CALL a COBOL program to execute on a thread that is not the current thread. Why would you do this? There are two main reasons:

  • There is something about the current thread that prevents COBOL execution, or
  • You want to (or must) continue working on the current thread while the program executes in parallel on another thread.

An example of the first case might be when the Java is running within a COBOL program and it wishes to execute another program completely independently. An example of the second when Java that is running on a user interface thread (UI thread) and cannot block while the program is running.

In first case, the call would likely be a blocking CALL (synchronous), and in the second case a non-blocking (asynchronous) call.

Calling COBOL Synchronously From Java

To call COBOL from Java when COBOL was not invoked initially, the Java program must obtain an instance of the Launcher class initially, and then call the program(s) as needed from within the Java class instance:

Configure the RuntimeOptions instance completely before constructing the DataUtil and Launcher instances. Public setters customize the mutable options object during setup; they are not a mechanism for reconfiguring runtime components that have already been initialized.

MyCaller.java
public class MyCaller {
    
    private static final DataUtil dataUtil;
    private static final Launcher launcher; 
    
    static {    
        RuntimeOptions runtimeOptions;    
        runtimeOptions = (RuntimeOptions) RunUnit.newRuntimeOptions("-c", "runtime.p3c");
        // the next line allows RETURNING arguments omitted by the caller to be fabricated and returned anyway
        runtimeOptions.setAutoBasingReturn(true);
        // the next line instantiates a COBOL data utility that is consistent with the current runtime option settings
        dataUtil = new DataUtil(runtimeOptions);
        /*
         * This is the Launcher object which will be used whenever the Java needs to run COBOL
         * business logic. This example instantiates a new program one each time the COBOL is called.
         * There are many options for more advanced behavior that may be explored in the Launcher API Javadocs.
         */
        launcher = Launcher.newLauncher(runtimeOptions);
    }
 
    /* 
     * Run a COBOL program from this instance of the MyCaller class.
     */
    public void runMyCobol() {
        
        // calling the "my-cobol" program with one argument and a return value
        long myAnswer = 0;          
        try {
            // the next line launches the "my-cobol" program in a new thread and awaits the result
            CobolPointer resultPointer = launcher.call("my-cobol", 42);
            // the call returned a CobolPointer instance; dereference the returned pointer to get its value
            myAnswer = dataUtil.getLong(resultPointer);
        } catch (P3RuntimeException p3e) {
            // any fatal error in the preceding will throw an exception; we dutifully catch it here
            System.err.println("something went wrong");         
        }
        // now we will print the returned answer
        System.out.println("MyCaller's answer is: " + myAnswer);
    }
}

This uses the "Launcher" method of calling COBOL. As such, it takes care of most of the tedious housekeeping and thread management.

As you see, the program first creates an instance of the P3/COBOL runtime options to use, then a DataUtil instance to work with the arguments passed to and from COBOL, and finally a Launcher instance. It declares a return argument into which it will retrieve the answer to the meaning of life (42).

💡

When using the Launcher approach, the actual COBOL program(s) are run in a different thread from the calling Java program. This is a particular advantage when calling from web applications or from a GUI thread. If you wish to invoke the COBOL on the same thread as the Java caller, a different approach is available.

It then launches the CALL, specifying the name of the COBOL program to be run, and an auto-boxed integer 42 as the actual argument to the COBOL program.

The auto-basing-return option causes the CALLed program to allocate its return argument as a convenience. This saves the Java caller from having to know how to create a pointable COBOL data area (a ByteStore object) and how to instantiate a CobolPointer to correctly point to that area. It also allows the Java program to be somewhat insensitive to the actual data type of the return argument in the COBOL program.

The arguments to the COBOL program must be “boxed” if they are Java primitive types, since an array of Object is expected.

The same DataUtil convenience methods as those used for embedded Java and Java called by COBOL can be used to access the return argument. Note that the actual data type within the called program is completely hidden from the Java caller. In this case we are only interested in printing a String representation of the return argument, so we do not care what type COBOL returns.

As long as non-array items are being passed between the Java and the called COBOL program, this is all it takes. The Launcher has many other modes that can pool groups of run units and allow the CALL to specify that the run unit used is serially reusable. More advanced techniques may be used to pass and receive data which is structured (for example, a COBOL group), but that, and more, will be left for a more advanced version of this topic and or the applicable Javadocs.

💡

As a reminder, constructing a skeleton COBOL version of what you are trying to do and then looking at the generated Java code will give the best idea of how to accomplish these more involved manipulations.

Calling C/C++ from P3/COBOL

Calling C and C++ from P3/COBOL is generally no different from calling COBOL. In order to locate the non-COBOL code, the shared library (or DLL) must be identified to the P3/COBOL system. Native calls are mediated by the Java Foreign Function and Memory API (not JNA).

In order to register the library for subsequent calling from the P3/COBOL program you should include in the runtime.p3c configuration file the following element:

runtime.p3c
<p3-runtime-options>
    <external-library-directories>
        <!-- space separated list of library paths -->
    </external-library-directories>
    <allocate-all-direct>true</allocate-all-direct>
</p3-runtime-options>

The first element tells the runtime which directories to search for dynamically loadable libraries (e.g., dll, so, dylink) and the second tells the P3/COBOL memory management to allocate all of its structures from global heap memory, rather than the JVM garbage-collected heap memory. The latter enables any arguments to the native programs to be passed by reference via pointers without unnecessary copying of the data.

If the native code does not need to call back into COBOL (usually the case), this is all you have to do. When the COBOL program calls the non-COBOL program each argument is converted into a native C-type as follows:

COBOL Data CategoryNative Argument Type
Alphanumeric, Edited, Groupchar*
BY REFERENCE numeric and Pointerchar*
BY VALUE integer (1 – 9 digits)long
BY VALUE integer (10 – 18 digits)long long
Numeric non-integerdouble
National, National editedchar* (0-terminated)

RETURNING/GIVING Arguments

If the COBOL CALL statement specifies RETURNING or GIVING the non-COBOL program is expected to return a native char*, which is then stored in the passed RETURNING item. Any receiving data item with USAGE other than POINTER will result in an runtime error.