Those who read my earlier blog article on accessing CICS programs via Liberty from WebServices, remember that I was announcing this article. So, let’s dive into it.
Integrating COBOL batch programs with modern Java applications running in CICS is a common requirement in enterprise mainframe environments. The External Call Interface (EXCI) provides the bridge, allowing batch programs to call into CICS programs and return results.
In this guide, we’ll build an end-to-end flow from COBOL batch → CICS → Liberty → Outbound WebService → reply to batch, using real code excerpts and an overview diagram to illustrate the process. Just as the illustration below shows this also works nicely for CICS → Liberty → Outbound WebService → reply to online in case an integration of services in online applications is required.
One will easily realise that this aproach enables to even replace existing modules in mainframe applications with available services from anywhere, as long as it is accessible from Java.
High-Level Architecture
Here’s the big picture of how the components interact:

Explanation of the diagram:
- Batch Program calls the CALLWS Handler program (EXIHDLR).
- EXIHDLR uses EXCI DPL (Distributed Program Link) to enter CICS.
- Inside CICS TS, a Mirror Transaction invokes DFHMIRS, which dispatches to the target program (
CALLWS00) that is implemented in Java (FlowManager) running on Liberty. - On the Liberty JVM server, the FlowManager invokes your Java Flow, that now invokes your desired service.
- Java flows can call external Web Services or Service Registries, and return results back through the same path. Other Flows can be implemented that do all other sorts of things one can do in Java, such as accessing any database via JDBC, interact with messaging services via JMS (e.g. Kafka), accessing anything accessible via TCP/IP.
The colors represent what you already have (blue), what you will get from Living Mainframe (green) and what you need to create (yellow) to incorporate services into your existing batch and online applications. This structure ensures modularity, separation of concerns, and reusability.
Batch Driver: EXIDEMO.cbl
The EXIDEMO program shows how a batch application interacts with the handler:
CALL EXIHDLR USING EXIHDLR-CA
IO-AREA-LEN
IO-AREA-PTR.
- EXIJDLR-CA → Control IO Area structured by EXIHDLR.cpy. It provides the ability to provide:
- PGMNAME the name of the program to be invoked (i.e. CALLWS00)
- MIRTX the mirror transaction code to use (e.g. EXCI)
- CICSID the CICS region ID/name (e.g. CICSTS62)
- USERID the userid to be used on the link to CICS
- APPID the application id to be used on the link to CICS (i.e. BATCHCLI)
- FLOWNAME the class name of the Flow to be executed, without its package
- CNRNAME the name to be used as a base for the payload request and reply containers
- STATUS a full blown status structure to tell exactly what happened in case the call went wrong
- IO-AREA-LEN → the number of bytes the IO-AREA is providing
- IO-AREA-PTR → the pointer to the buffer that keep the input data for the flow and will receive response of the flow.
This makes it easy for other batch programs to reuse the same handler logic.
Handler Logic: EXIHDLR.cbl
The EXIHDLR handler is responsible for managing containers and the EXCI call.
LINKAGE SECTION.
01 EXIHDLR-CA.
copy EXIHDLR.
01 IO-AREA-LEN PIC 9(8) COMP.
01 IO-AREA-PTR USAGE POINTER.
01 IO-AREA PIC X.
PROCEDURE DIVISION USING EXIHDLR-CA IO-AREA-LEN IO-AREA-PTR.
MAIN-LOGIC.
PERFORM IS-TRACE-ENABLED.
IF TRACE-ENABLED = 'Y'
DISPLAY '> EXIHDLR'
END-IF.
SET ADDRESS OF IO-AREA TO IO-AREA-PTR
* 1 DFHXCIS INIT-USER
PERFORM EXCI-INIT-USER.
* 2 DFHXCIS ALLOC-PIPE
PERFORM EXCI-ALLOC-PIPE.
* 3 DFHXCIS OPEN-USER
PERFORM EXCI-OPEN-PIPE.
* 4 DFHXCIS EXEC CICS PUT CONTAINER
PERFORM EXEC-PUT-CTRL-CNR.
* 5 DFHXCIS EXEC CICS PUT CONTAINER
PERFORM EXEC-PUT-DATA-CNR.
* 6 DFHXCIS DPL-REQUEST
PERFORM EXCI-DPL-LINK.
* 7 DFHXCIS EXEC CICS GET CONTAINER
PERFORM EXEC-GET-CTRL-CNR.
* 8 DFHXCIS EXEC CICS GET CONTAINER
PERFORM EXEC-GET-DATA-CNR.
* 9 DFHXCIS DELETE CHANNEL, CLOSE-PIPE, DEALLOCATE-PIPE
PERFORM EXCI-CLEANUP.
PERFORM FINISH-PROGRAM.
IF TRACE-ENABLED = 'Y'
DISPLAY '< EXIHDLR'
END-IF.
GOBACK.
Step 1 to 3 will establish the connection to CICS and have the environment ready to then provide the data.
Step 4 and 5 will provide the two input containers on a implicitly created channel.
Step 6 will invoke the java programm sending the data in the containers along.
Step 7 will obtain the response from CALLWS00 which is implemented by FlowManager.run method
Step 8 will obtain the reponse from the FLow that FlowManager invoked.
Any applicable error handling has been removed from the code snippet to keep the essential sequence in focus.
Java Flow Manager: FlowManager.java
On the Liberty JVM server, FlowManager receives the request, executes the business logic, and returns results.
public class FlowManager {
@CICSProgram("CALLWS00")
public void run(){
logger.entering(cname, "run()");
Task task = getTask();
try {
Channel ch = getChannel(task);
ControlData ctrl = getControlContainer(task, ch);
ctrl.setReturncode(0);
ctrl.setMsgCode(" ");
// Instantiate the Flow based on its Name provided by the caller
IFlow flow = FlowFactory.getFlow(ctrl.getFlowName());
if (flow != null) {
// Obtain the Container for the Flow that will be instantiated
Container data = getDataContainer(task, ch,ctrl);
// the flow implementation is now called and a reply
// will be captured in a byte[]
byte[] response = flow.execute(data.get());
if (response != null && response.length > 0) {
setDataContainer(ch,ctrl, response);
} else {
// an empty result will generate a warning, but no error
ctrl.setReturncode(4);
ctrl.setMsgCode("WSC00001I");
}
} else {
logger.log(Level.SEVERE
,"Unable to create instance of class '%s'"
,ctrl.getFlowName());
task.abend("FLWE");
}
setControlContainer(ch,ctrl);
printContainersOnChannel(task);
} catch (ContainerErrorException e){
logger.log(Level.SEVERE, e.getMessage(), e);
if (task != null) {
task.abend("CNRE");
}
} catch (ChannelErrorException e){
logger.log(Level.SEVERE, e.getMessage(), e);
if (task != null) {
task.abend("CHNE");
}
} catch (LengthErrorException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
if (task != null) {
task.abend("LENE");
}
} catch (CCSIDErrorException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
if (task != null) {
task.abend("CISE");
}
} catch (CodePageErrorException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
if (task != null) {
task.abend("CPEE");
}
} catch (InvalidRequestException e) {
logger.log(Level.SEVERE, e.getMessage(), e);
if (task != null) {
task.abend("IVRE");
}
} catch (NullPointerException e) {
logger.log(Level.SEVERE,e.getMessage(), e);
if (task != null) {
task.abend("NLPE");
}
}
logger.exiting(cname, "run()");
return;
}
}
The FlowManager run method:
- obtains the ControlContainer – It needs to know the FLOWNAME and the CNRNAME, so it is able to instantiate the Flow and pass the data to it.
- uses the FlowFactory – passing the FLOWNAME to obtain a reference to an IFlow object.
- obtaines the Data input container – containing the request data only relevant and understood by that Flow object. To the FlowManage it is just bytes.
- invoke the flow.execute method – providing the data request, capturing the flow’s response
- inserting the flow’s response into the data output container – channeling through the response data to the caller
- returning control to the caller
All of the exception handling is shown here to emphasize on the fact that this solution is providing most detailed analysis capability to the caller analyzing what happened.
* Last function of handler executed
05 EXIHDLR-STATUS.
07 EXIHDLR-FUNCTION PIC X(10) VALUE SPACES.
07 EXIHDLR-RETURNCODE PIC S9(8) VALUE 0.
07 EXIHDLR-FLOW-RETURN-CODE.
09 EXIHDLR-FLOW-RC PIC X(8) VALUE SPACES.
09 EXIHDLR-FLOW-MSG PIC X(8) VALUE SPACES.
07 EXIHDLR-EXEC-RETURN-CODE.
09 EXIHDLR-RESP PIC S9(8) VALUE 0.
09 EXIHDLR-RESP2 PIC S9(8) VALUE 0.
09 EXIHDLR-ABCODE PIC X(4) VALUE SPACES.
07 EXIHDLR-EXCI-RETURN-CODE.
09 EXIHDLR-RESPONSE PIC S9(8) VALUE 0.
09 EXIHDLR-REASON PIC S9(8) VALUE 0.
09 EXIHDLR-SUB-REASON1 PIC S9(8) VALUE 0.
09 EXIHDLR-SUB-REASON2 PIC S9(8) VALUE 0.
The EXIHDLR-STATUS substructure of the EXIHDLR copybook confirms the level of detail provided. For the caller all required is to check EXIHLDR-RETURNCODE being 0. Then the call went overall ok. If that is not zero EXIHDLR-FUNCTION will tell which of the steps in EXIHDLR failed. Depending on that step being an EXCI or EXEC call, different return codes are provided. If it was the FlowManage unable to fulfill the request, it will provide its returncode and message-code as well.
Note: Application specific state is communicated in the payload. The EXIHDLR-STATUS will not indicate if your business related function call succeeded achiving what you asked it to do. Clearly when asking to find a certain data item the information that the service was unable to retreive that based on the given input data, is part of the application specific protocol communicated soly via the <CNRNAME>_DATA_I and <CNRNAME>_DATA_O container content.
In our sample we simply DISPLAYED EXIHDLR-STATUS in EXIHDLR-RETURNCODE NOT EQ 0 and recieved a PSW like string in the log, that can easily be analyzed.
End-to-End Flow Recap
- Batch program (
EXIDEMO) → Calls handler (EXIHDLR). - Handler builds containers and invokes CICS via EXCI.
- CICS mirror transaction (
DFHMIRS) routes to JavaCALLWS00. FlowManagerexecutes logic, interacts with flows and services.- Results and status flow back to batch via containers.
What to configure in CICS
This article assumes you have a Liberty already set up. CICS comes with a set of default definitions, particularly transactions. Check if there is already a mirror transaction defined (i.e. EXCI) in you region.
Finally, the CICS admin needs to define a program resource to that region. The most essential parameters are depicted here:
CEDA DEFINE PROGRAM (CALLWS00)
GROUP () - the group you use for that in your CICS; we used "TESTGRP"
JVM (YES)
JVMCLASS (wlp:io.lm.cics.wlp.flows.FlowManager#run)
JVMSERVER () - the name of your Liberty server; we used "LIBERTY"
CEDA INSTALL PROGRAM (CALLWS00)
Why This Matters
- Reusability –
EXIHDLRcan be shared across many batch programs, invoked staticly or dynamicly. - Clarity – Control (
CALLWS_DATA_*) separated from payload (<CNRNAME>_DATA_*). Separation of input and output using a DATA_I and a separate DATA_O container enabling always to look back into what was the request to a response. - Flexibility – Container names can be parameterized.
- Resilience – Centralized error handling in
EXIHDLR.cpy. - Interoperability – COBOL and Java cooperate seamlessly in CICS.
Future enhancements
To boost performace it might be useful to enhance EXIHDLR program to only
- execute Step 1 – 3 once
- invoking Step 4 – 8 as often as required in the batch processing
- invoking Step 9 finally closing and cleaning up
We consider providing the ability to invoke the handler with modes:
- Establish the connection to CICS
- Invoke a flow, could be a different one each time, could be the same one each time, makes no difference.
- Close the connection to CICS and clean up
- Do 1 through 3 all in one go, just like today
Conclusion
With this setup, we achieve a robust pattern for batch & online to outside world integration. The EXCI API, mirror transaction, FlowManager, and container strategy together ensure clean separation of concerns, easier debugging, and extensibility for future flows.
This architecture serves as a template for extending COBOL batch programs into the modern service landscape while leveraging the full capabilities of Java and CICS Liberty.