This article describes how to set up a simple environment for running Java batch reports or utilities, including JCL PROCs similar to the compile and go PROCs that come with other languages.
Java on z/OS is great. Fast and powerful, you can perform complex functions in very few lines of code. In the past, I have used Rexx for small utilities, but now Java is my first choice.
Java is an advanced language with many features that simplify programming problems. For example, the Collections framework provides various List, Map, Set etc. classes that allow you to quickly and efficiently create, search and compare groups of data items.
The JZOS Toolkit comes with Java on z/OS and provides interfaces for working with regular z/OS datasets.
Java should be in every systems programmer’s toolbox.
The problem is there is not much guidance on how to create a simple Java batch environment on z/OS. Most of what is out there is written for large Java applications rather than simple batch jobs and as a result is unnecessarily complex.
The Objective
To create a set of procs for batch Java Compile, Go, and Compile and Go (there is no Link step for Java).
Compiling and running a Java class should be as simple as:
//ANDREWRG JOB CLASS=A,
// MSGCLASS=H,
// NOTIFY=&SYSUID
//JAVACG EXEC JAVACG,
// JAVACLS='''MyClass'''
//G.INPUT DD DISP=SHR,DSN=...
Optionally, you need to be able to override the locations for Java source, Java class files, class path etc. and the various Java options that can be specified.
The compile and link steps should be consistent so that you can switch between JAVAC, JAVAG and JAVACG without making other changes to the JCL.
Prerequisites
First, obviously, if Java is not already installed you need to install it. Refer to IBM 31-bit SDK for z/OS, Java Technology Edition, Version 8 and/or IBM 64-bit SDK for z/OS, Java Technology Edition, Version 8 for Java installation instructions.
JZOS Batch Launcher
You also need to install the JZOS Batch Launcher. This provides the functions that make Java useful in a z/OS batch environment.
Installation is straightforward. There is one load module to copy to a PDS/E, a JCL procedure and a sample job. The Readme suggests copying the load module to SYS1.SIEALNKE, however this is a SMP/E maintained dataset so if you are installing outside of SMP/E you should use a different location – create a new PDS/E if necessary.
JZOS documentation can be found here:
z/OS 2.1
These PROCs use symbol substitution in instream data which was introduced with z/OS 2.1, so z/OS 2.1 is a prerequisite. The JCL procedure delivered with JZOS works with earlier versions, but in that case the JCL required in the calling job is more complicated.
JCL Procedures
Most languages on z/OS come with a set of compile, link and go JCL procedures. These allow you to compile and run programs using standard options without dealing with the gory details every time.
We can create the same thing for Java, which greatly simplifies the process of compiling and running Java batch programs. I will be calling the PROCs JAVAC
, JAVAG
and JAVACG
.
Design Considerations
Default Locations
Java source and class files need to be in the unix filesystem.
A standard directory structure will mean that we can set a default Java CLASSPATH etc. in the PROC, to simplify the user’s JCL.
We will use:
~/java/src
for the Java source~/java/target
for the output class files
(~ is shorthand for the user’s home directory)
These can be overridden by the SRCPATH
and TGTPATH
parameters of the PROCs.
Line length
Instream data in the proc is limited to 80 bytes. If the symbol substitution extends the line beyond 80 bytes, the step gets a S001 abend. This is most likely if multiple symbols are used on one line.
To get around the problem, where we need multiple substitutions on 1 line we can assign the values to shell variables, and then use the shell variables. They are substituted later by the Unix shell which doesn’t have the same restrictions.
Java Classpath
The Java classpath is potentially lengthy with multiple entries. To allow for longer classpaths multiple CLASPAT* parameters are provided on the PROC.
To make life more complicated, the classpath syntax for the JZOS Batch Launcher is different from the Java compiler classpath syntax. The Java compiler allows wildcards but JZOS does not. We want to use the same classpath parameters in the JCL for the compile and go steps, so we need to expand wildcards in the JZOS script. To make sure the compile and go steps use the same classpath, we do the same expansion for the compile step.
Why triple quotes?
The triple quotes (apostrophes) are a quirk of JCL, which seems to be required for lowercase parameters. The enclosing quotes are stripped off when the parameters are set. The result still needs to be quoted otherwise the lowercase data causes a JCL error.
JAVAC
JAVAC is the Java compile PROC. It invokes BPXBATCH to run the Java compiler.
//JAVAC PROC JAVACLS=,
// REGSIZE='0M',
//* Parameters for instream data need triple quotes
//* to preserve quotes in the SET statement
// SRCPATH='''java/src''',
// TGTPATH='''java/target''',
// CLASPATH='''''',
//* Additional CLASPAT* entries allow CLASSPATH to
//* be extended with less JCL continuation issues.
// CLASPAT2='''''',
// CLASPAT3='''''',
// CLASPAT4='''''',
// CLASPAT5='''''',
//* Options to the java compiler
// JAVACOPT=''''''
//*
//SYMBOLS EXPORT SYMLIST=(JAVACOPT,
// SRCPATH,
// TGTPATH,
// CLASPATH,
// CLASPAT2,
// CLASPAT3,
// CLASPAT4,
// CLASPAT5,
// JAVACLS)
//*
// SET JAVACLS=&JAVACLS
// SET SRCPATH=&SRCPATH
// SET TGTPATH=&TGTPATH
// SET CLASPATH=&CLASPATH
// SET CLASPAT2=&CLASPAT2
// SET CLASPAT3=&CLASPAT3
// SET CLASPAT4=&CLASPAT4
// SET CLASPAT5=&CLASPAT5
// SET JAVACOPT=&JAVACOPT
//*
//C EXEC PGM=BPXBATCH,REGION=®SIZE
//* STDPARM is multiple commands chained into one with
//* semicolons as an argument to "SH"
//* IFS splits CLASSPATH based on ":"
//* Then wildcards are expanded and recombined
//STDPARM DD *,SYMBOLS=JCLONLY
SH CLASSPATH=&TGTPATH;
IFS=":";
for i in &CLASPATH; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${i}";
done;
done;
for i in &CLASPAT2; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${i}";
done;
done;
for i in &CLASPAT3; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${i}";
done;
done;
for i in &CLASPAT4; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${i}";
done;
done;
for i in &CLASPAT5; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${i}";
done;
done;
export CLASSPATH="${CLASSPATH}";
SRCPATH=&SRCPATH;
TGTPATH=&TGTPATH;
JAVACLS=&JAVACLS;
/usr/lpp/java/J8.0/bin/javac
&JAVACOPT
-sourcepath
${SRCPATH}
-d
${TGTPATH}
${SRCPATH}/${JAVACLS}.java
//STDENV DD DDNAME=ENV
// DD DDNAME=ADDENV
//ADDENV DD DISP=SHR,DSN=NULLFILE
//ENV DD *
HJV_JZOS_JVM_SMF_LOGGING=true
HJV_JZOS_JVM_SMF_LOGGING_INTERVAL=10
HJV_JZOS_JVM_SMF_THREADS=true
//STDOUT DD SYSOUT=*
//STDERR DD SYSOUT=*
//*
// PEND
JAVAC Examples
//ANDREWRG JOB CLASS=A,
// MSGCLASS=H,
// NOTIFY=&SYSUID
//JAVACG EXEC PROC=JAVAC,
// JAVACLS='''helloworld'''
//ANDREWRC JOB CLASS=A,
// MSGCLASS=H,
// NOTIFY=&SYSUID
//*
//JAVAC EXEC JAVAC,
// JAVACLS='''com/blackhillsoftware/samples/RecordCount''',
// SRCPATH='''java/easysmf-je-1-5-2/samples/source''',
// JAVACOPT='''-Xlint -verbose''',
// CLASPATH='''java/easysmf-je-1-5-2/jar/*'''
STDENV, ENV and ADDENV DD Statements
The STDENV DD statement allows you to specify enviroment variables for the BPXBATCH step.
STDENV concatenates 2 other DD statements:
ENV
has default environment variables you want to set for all users of the proc.ADDENV
is a null file which can be overridden by the calling job to add or modify the default environment
//C.ADDENV DD *
MYVARIABLE=ABCD
CLASSPATH
CLASSPATH can be set using wildcards or explicit entries. You can have multiple entries on one line separated by “:”, e.g.
// CLASPATH='''java/easysmf-je-1-5-2/jar/*''',
// CLASPAT2='''java/lib/javax.mail.jar:java/lib/jsoup-1.10.2.jar'''
The shell script sets the field separator (IFS) to “:” so it can split multiple entries in the class path and expand any wildcards, before recombining all the entries.
JAVAG
JAVAG is based on the JVMPRC80 and JVMJCL80 samples delivered with the JZOS Batch Launcher.
The embedded script has been modified to set the classpath using the same syntax as the JAVAC proc.
//JAVAG PROC JAVACLS=,
// ARGS=,
// LIBRARY='PDSE.CONTAINING.JVMLDM80',
// LOGLVL='',
// REGSIZE='0M',
// LEPARM='',
//* Parameters for instream data need triple quotes
//* to preserve quotes in the SET statement
// SRCPATH='''java/src''',
// TGTPATH='''java/target''',
// CLASPATH='''''',
//* Additional CLASPAT* entries allow CLASSPATH to
//* be extended with less JCL continuation issues.
// CLASPAT2='''''',
// CLASPAT3='''''',
// CLASPAT4='''''',
// CLASPAT5=''''''
//*
//SYMBOLS EXPORT SYMLIST=(JAVACOPT,
// SRCPATH,
// TGTPATH,
// CLASPATH,
// CLASPAT2,
// CLASPAT3,
// CLASPAT4,
// CLASPAT5,
// JAVACLS)
//*
// SET JAVACLS=&JAVACLS
// SET SRCPATH=&SRCPATH
// SET TGTPATH=&TGTPATH
// SET CLASPATH=&CLASPATH
// SET CLASPAT2=&CLASPAT2
// SET CLASPAT3=&CLASPAT3
// SET CLASPAT4=&CLASPAT4
// SET CLASPAT5=&CLASPAT5
//*
//G EXEC PGM=JVMLDM80,REGION=®SIZE,
// PARM='&LEPARM/&LOGLVL &JAVACLS &ARGS'
//*
//STEPLIB DD DSN=&LIBRARY,DISP=SHR
//SYSPRINT DD SYSOUT=*
//SYSOUT DD SYSOUT=*
//STDOUT DD SYSOUT=*
//STDERR DD SYSOUT=*
//CEEDUMP DD SYSOUT=*
//ABNLIGNR DD DUMMY
//STDENV DD DDNAME=ENV
// DD DDNAME=ADDENV
//ADDENV DD DISP=SHR,DSN=NULLFILE
//ENV DD *,SYMBOLS=JCLONLY
. /etc/profile
export JAVA_HOME=/usr/lpp/java/J8.0
export PATH=/bin:"${JAVA_HOME}"/bin
LIBPATH=/lib:/usr/lib:"${JAVA_HOME}"/bin
LIBPATH="$LIBPATH":"${JAVA_HOME}"/lib/s390
LIBPATH="$LIBPATH":"${JAVA_HOME}"/lib/s390/j9vm
LIBPATH="$LIBPATH":"${JAVA_HOME}"/bin/classic
export LIBPATH="$LIBPATH":
APP_HOME=&TGTPATH
CLASSPATH="${APP_HOME}"
IFS=':'
for i in &CLASPATH; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${j}"
done
done
for i in &CLASPAT2; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${j}"
done
done
for i in &CLASPAT3; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${j}"
done
done
for i in &CLASPAT4; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${j}"
done
done
for i in &CLASPAT5; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${j}"
done
done
export CLASSPATH="${CLASSPATH}"
IJO="-Xms16m -Xmx128m"
export IBM_JAVA_OPTIONS="$IJO "
export HJV_JZOS_JVM_SMF_LOGGING=true
export HJV_JZOS_JVM_SMF_LOGGING_INTERVAL=10
export HJV_JZOS_JVM_SMF_THREADS=true
// PEND
JAVAG Examples
//JAVAG EXEC PROC=JAVAG,
// JAVACLS='''helloworld'''
//*JAVACLS='''com/blackhillsoftware/ivp'''
//*JAVACLS='''-jar /u/ajr/java/helloworld.jar'''
//G.INPUT DD DISP=SHR,DSN=ANDREWR.SMF.SAMPLE
//G.ADDENV DD *
IJO="$IJO -verbose:class"
export IBM_JAVA_OPTIONS="$IJO "
This sample JCL shows 3 different types of Java class that can be invoked:
- A class which is not part of a package.
- A class (ivp) which is a member of package com.blackhillsoftware.
- A runnable jar.
//G.INPUT
is an example of a DDNAME used by a particular Java program, not a DD required by the PROC.
STDENV, ENV and ADDENV DD Statements
The STDENV DD statement provides a script that is run before the Java program is invoked. It is used to set environment variables.
Unfortunately, the syntax is different to STDENV for BPXBATCH in the compile step. BPXBATCH STDENV sets the environment variables directly, while JZOS requires a script. The JZOS approach adds some flexibility, but means you need to explicitly “export” the variables.
As with the compile step, STDENV concatenates 2 other DD statements:
ENV
has the standard script inline in the PROC.ADDENV
is a null file which can be overridden by the calling job to add or modify the default environment.
//G.ADDENV DD *
IJO="$IJO -verbose:class"
export IBM_JAVA_OPTIONS="$IJO "
CLASSPATH
CLASSPATH is set using the same syntax as the compile PROC.
JAVACG
JAVACG combines the C and G PROCs, with a condition code check after the BPXBATCH compile step.
//JAVACG PROC JAVACLS=,
// ARGS=,
// LIBRARY='PDSE.CONTAINING.JVMLDM80',
// LOGLVL='',
// REGSIZE='0M',
// LEPARM='',
//* Parameters for instream data need triple quotes
//* to preserve quotes in the SET statement
// SRCPATH='''java/src''',
// TGTPATH='''java/target''',
// CLASPATH='''''',
//* Additional CLASPAT* entries allow CLASSPATH to
//* be extended with less JCL continuation issues.
// CLASPAT2='''''',
// CLASPAT3='''''',
// CLASPAT4='''''',
// CLASPAT5='''''',
//* Options to the java compiler
// JAVACOPT=''''''
//*
//SYMBOLS EXPORT SYMLIST=(JAVACOPT,
// SRCPATH,
// TGTPATH,
// CLASPATH,
// CLASPAT2,
// CLASPAT3,
// CLASPAT4,
// CLASPAT5,
// JAVACLS)
//*
// SET JAVACLS=&JAVACLS
// SET SRCPATH=&SRCPATH
// SET TGTPATH=&TGTPATH
// SET CLASPATH=&CLASPATH
// SET CLASPAT2=&CLASPAT2
// SET CLASPAT3=&CLASPAT3
// SET CLASPAT4=&CLASPAT4
// SET CLASPAT5=&CLASPAT5
// SET JAVACOPT=&JAVACOPT
//*
//C EXEC PGM=BPXBATCH,REGION=®SIZE
//* STDPARM is multiple commands chained into one with
//* semicolons as an argument to "SH"
//* IFS splits CLASSPATH based on ":"
//* Then wildcards are expanded and recombined
//STDPARM DD *,SYMBOLS=JCLONLY
SH CLASSPATH=&TGTPATH;
IFS=":";
for i in &CLASPATH; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${i}";
done;
done;
for i in &CLASPAT2; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${i}";
done;
done;
for i in &CLASPAT3; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${i}";
done;
done;
for i in &CLASPAT4; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${i}";
done;
done;
for i in &CLASPAT5; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${i}";
done;
done;
export CLASSPATH="${CLASSPATH}";
SRCPATH=&SRCPATH;
TGTPATH=&TGTPATH;
JAVACLS=&JAVACLS;
/usr/lpp/java/J8.0/bin/javac
&JAVACOPT
-sourcepath
${SRCPATH}
-d
${TGTPATH}
${SRCPATH}/${JAVACLS}.java
//STDENV DD DDNAME=ENV
// DD DDNAME=ADDENV
//ADDENV DD DISP=SHR,DSN=NULLFILE
//ENV DD *
HJV_JZOS_JVM_SMF_LOGGING=true
HJV_JZOS_JVM_SMF_LOGGING_INTERVAL=10
HJV_JZOS_JVM_SMF_THREADS=true
//STDOUT DD SYSOUT=*
//STDERR DD SYSOUT=*
//*
//G EXEC PGM=JVMLDM80,REGION=®SIZE,
// PARM='&LEPARM/&LOGLVL &JAVACLS &ARGS',
// COND=(0,NE,C)
//*
//STEPLIB DD DSN=&LIBRARY,DISP=SHR
//SYSPRINT DD SYSOUT=*
//SYSOUT DD SYSOUT=*
//STDOUT DD SYSOUT=*
//STDERR DD SYSOUT=*
//CEEDUMP DD SYSOUT=*
//ABNLIGNR DD DUMMY
//STDENV DD DDNAME=ENV
// DD DDNAME=ADDENV
//ADDENV DD DISP=SHR,DSN=NULLFILE
//ENV DD *,SYMBOLS=JCLONLY
. /etc/profile
export JAVA_HOME=/usr/lpp/java/J8.0
export PATH=/bin:"${JAVA_HOME}"/bin
LIBPATH=/lib:/usr/lib:"${JAVA_HOME}"/bin
LIBPATH="$LIBPATH":"${JAVA_HOME}"/lib/s390
LIBPATH="$LIBPATH":"${JAVA_HOME}"/lib/s390/j9vm
LIBPATH="$LIBPATH":"${JAVA_HOME}"/bin/classic
export LIBPATH="$LIBPATH":
APP_HOME=&TGTPATH
CLASSPATH="${APP_HOME}"
IFS=':'
for i in &CLASPATH; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${j}"
done
done
for i in &CLASPAT2; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${j}"
done
done
for i in &CLASPAT3; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${j}"
done
done
for i in &CLASPAT4; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${j}"
done
done
for i in &CLASPAT5; do
for j in ${i}; do
CLASSPATH="${CLASSPATH}":"${j}"
done
done
export CLASSPATH="${CLASSPATH}"
IJO="-Xms16m -Xmx128m"
export IBM_JAVA_OPTIONS="$IJO "
export HJV_JZOS_JVM_SMF_LOGGING=true
export HJV_JZOS_JVM_SMF_LOGGING_INTERVAL=10
export HJV_JZOS_JVM_SMF_THREADS=true
// PEND
JCL to invoke it looks like:
//JAVAC EXEC PROC=JAVA8UCG,
// JAVACLS='smf/joblist'
//*
//* DD statement for the Java program
//G.INPUT DD DISP=SHR,DSN=SMF.DATA
//* Optional DD statements to demonstrate
//* modifing compile/go environments
//G.ADDENV DD *
IJO="$IJO -verbose:class"
export IBM_JAVA_OPTIONS="$IJO "
Writing your Java program
Most examples suggest using an IDE like Eclipse to write and compile your program, transferring the class files to z/OS to run.
Eclipse is a very productive environment for writing Java and if you are used to using Eclipse, writing Java without it is painful. However, writing Java is no different to writing in any other language on z/OS and the simplicity of working with a single platform and familiar tools has a lot to recommend it.
I suggest getting a simple batch environment working on z/OS, and only then expanding the environment to use Eclipse, build and deploy scripts etc. if required. It is much easier to setup and debug more complex functions if you start with a simple, working batch environment.
Java programs must live in Unix directories due to the way Java uses the directory structure. ISPF option 3.17 provides a nice environment for editing z/OS unix files.
Tip: ISPF highlighting doesn’t have a Java option, but setting the language to C works pretty well for matching braces, showing comments etc.
Those pesky class not found errors
First efforts to run Java on z/OS often result in mystifying class not found errors. You look at the CLASSPATH, you can see the Java class file there, why can’t Java find it?
The answer lies in how Java classes are organized into packages. Packages avoid naming conflicts between classes, but Java requires that the classes are located in subdirectories that reflect the package name.
z/OS (and Windows/Unix for that matter), searches the exact locations specified (LINKLIST, STEPLIB, path etc) for executable programs. Java searches for classes in locations relative to the CLASSPATH directory, based on the class package name.
If we have a simple Java program:
public class helloworld {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
This program doesn’t specify a package, so the source and class files must be in the directories specified in the SOURCEPATH and CLASSPATH.
However, if we decide to create a “helloworld” package and a “testhello” package (perhaps to share the function with other programs):
package testhello;
// This class doesn't do anything
// until called from another class
public class hello {
public static void sayHello() {
System.out.println("Hello World!");
}
}
helloworld becomes:
package helloworld;
import testhello.hello;
// This class has a main(...) method
// which java calls to invoke the program
public class helloworld {
public static void main(String[] args) {
hello.sayHello();
}
}
The helloworld class is in the helloworld package, so the PROCs specify:
// JAVACLS='helloworld/helloworld'
If the runtime CLASSPATH is set to
/u/andrew/java
Java will expect to find /u/andrew/java/helloworld/helloworld.class
and /u/andrew/java/testhello/hello.class
.
If your CLASSPATH is set to /u/andrew/java
and the hello.class
and helloworld.class
files are located in that directory, Java will say they cannot be found… which can be puzzling to say the least. The answer is that the location relative to the CLASSPATH must match the package name.
Java source files are also required to use the same hierarchy. A class in package mypackage
MUST be in the mypackage/
subdirectory or you will get a compilation error.
Actually, that’s not completely true… if you point the compiler at a Java file that specifies a package, that class will compile OK. It will also find other classes in the same package if they are in the same directory. However, it will locate other classes by going up the directory tree to what it considers the package root, and then looking for subdirectories according to package names.
The class files produced will be placed in subdirectories of the output directory.
This means that Java source code on z/OS must be in HFS directories – unfortunately, you can’t use regular z/OS datasets for your source code.
Jar files follow the same principle, except that the directory hierarchy is embedded in the jar file itself. You can see this if you extract a jar file with a zip program.
Naming packages
To avoid name clashes there is a naming convention for Java packages. According to convention, the package name begins with your internet domain name reversed, i.e. Black Hill Software uses com.blackhillsoftware
. Subsequent components are based on your own naming conventions.
You might choose not to use the package naming conventions for your own programs, however if usage becomes more formalized e.g. you develop a library of commonly used functions it is a good idea to use names that adhere to the standard.
Either way, packages provide a good way to organize your programs inside the ~/java/src
etc. directory structure. You can separate programs into packages, which allows you to organize the programs into subdirectories based on package name.
Conclusion
A set of JCL procedures for compiling and running Java programs makes Java much more useful on z/OS.
Once you can easily compile and run Java on z/OS it becomes an excellent language for writing small batch utilities and productivity tools.
What Next?
Why not try using Java for SMF reporting : EasySMF:JE Java Quickstart