Every COBOL program that runs on an IBM mainframe requires Job Control Language (JCL) to execute. While COBOL tells the computer what to process, JCL tells the operating system how to run the processing: which program to execute, where to find the...
In This Chapter
- Introduction: The Language of z/OS Batch
- JCL Statement Types
- JCL Syntax Rules
- The JOB Statement
- The EXEC Statement
- The DD Statement: Defining Datasets
- Procedures (PROCs)
- Conditional Execution
- Dataset Types
- Common Utilities
- JCL for COBOL Development
- Common JCL Errors and Diagnosis
- JES2/JES3 Job Processing
- Best Practices
- The COBOL-JCL Connection: Tying It All Together
- Summary
Chapter 27: JCL Essentials for COBOL Programmers
Introduction: The Language of z/OS Batch
Every COBOL program that runs on an IBM mainframe requires Job Control Language (JCL) to execute. While COBOL tells the computer what to process, JCL tells the operating system how to run the processing: which program to execute, where to find the input files, where to place the output, how much memory to allocate, and what to do if something goes wrong. Understanding JCL is not optional for a COBOL programmer working in a mainframe environment -- it is fundamental.
JCL has been part of the IBM mainframe ecosystem since OS/360 in 1964. Despite its age, JCL remains the primary mechanism for submitting and controlling batch work on z/OS. Every one of the billions of batch transactions that flow through the world's banking, insurance, and government systems each day is orchestrated by JCL.
This chapter provides a thorough treatment of JCL from the COBOL programmer's perspective. You will learn how to write JCL to compile, link-edit, and execute COBOL programs; how to define and manage the datasets your programs read and write; how to build multi-step jobs with conditional execution; how to create reusable procedures; and how to work with the utility programs that are essential to daily mainframe operations.
JCL Statement Types
Every JCL statement begins with a double slash (//) in columns 1 and 2. There are five types of JCL statements:
| Statement | Identifier | Purpose |
|---|---|---|
| JOB | //jobname JOB |
Marks the beginning of a job and defines job-level attributes |
| EXEC | //stepname EXEC |
Identifies a program or procedure to execute |
| DD | //ddname DD |
Defines a dataset (file) used by a step |
| Comment | //* |
A comment line (no effect on execution) |
| Delimiter | /* |
Marks the end of instream data |
There are also a few special statements: the null statement (//), which marks the end of a job; the JCLLIB statement, which identifies procedure libraries; and the INCLUDE statement, which includes JCL members from a library.
JCL Syntax Rules
JCL has strict syntax rules that date from the era of punched cards:
Columns 1-2: Must contain // for JCL statements, //* for comments, or /* for delimiters.
Name field (columns 3-10): An optional name that identifies the statement. A name must begin in column 3, start with a letter or national character ($, #, @), and can be up to 8 characters long. It is followed by at least one blank.
Operation field: The keyword (JOB, EXEC, DD, etc.) that identifies the statement type. It follows the name field (or begins after // if there is no name), separated by at least one space.
Operand field: The parameters that define the statement's behavior. Parameters are separated by commas with no embedded blanks. The operand field begins after at least one blank following the operation field.
Continuation: If a statement must continue onto the next line, you break the operand field after a complete parameter (after a comma), code // in columns 1-2 of the next line, and continue the operands starting between columns 4 and 16. The continued line must not start in column 3.
//PAYFILE DD DSN=PROD.PAYROLL.OUTPUT,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(5,2),RLSE),
// DCB=(RECFM=FB,LRECL=200,BLKSIZE=0),
// UNIT=SYSDA
In this example, each continued line begins with // followed by spaces to align the parameters for readability. The alignment is a convention, not a requirement -- the continuation simply must begin between columns 4 and 16.
The JOB Statement
The JOB statement is always the first statement in a job. It identifies the job to the system and specifies job-level attributes.
Syntax
//jobname JOB (accounting-info),'programmer-name',parameters
Key Parameters
Accounting information: Typically an account number and department code used for charge-back:
//PAYROLL JOB (ACCT001,'DEPT-42'),
Programmer name: Identifies the job owner. Must be enclosed in apostrophes if it contains special characters:
// 'JANE SMITH',
CLASS: Assigns the job to an input class, which determines the execution environment. Classes are defined by the installation (typically A through Z and 0 through 9):
// CLASS=A,
MSGCLASS: Specifies the output class for JCL messages and allocation messages. Common values: X for held output, A for immediate print:
// MSGCLASS=X,
MSGLEVEL=(statements,messages): Controls what JCL and allocation messages appear in the job log: - First subparameter (0, 1, or 2): 0 = JOB statement only, 1 = all JCL including proc expansions, 2 = input JCL only - Second subparameter (0 or 1): 0 = allocation messages only if job abends, 1 = all allocation messages
// MSGLEVEL=(1,1),
The value (1,1) is the most useful for debugging because it shows all JCL (including expanded procedures) and all allocation messages.
NOTIFY: Sends a message to the specified TSO user ID when the job completes. The symbol &SYSUID resolves to the submitting user's ID:
// NOTIFY=&SYSUID,
REGION: Specifies the maximum amount of virtual storage (memory) available to each step. REGION=0M requests the installation maximum:
// REGION=0M,
TIME: Maximum CPU time for the entire job. TIME=1440 means no time limit (1440 minutes = 24 hours). You can also code TIME=(minutes,seconds):
// TIME=1440
TYPRUN: Controls special job processing. TYPRUN=SCAN checks JCL syntax without executing the job. TYPRUN=HOLD holds the job in the input queue:
// TYPRUN=SCAN
COND: A job-level condition that can cause the entire job to terminate. If any step's return code meets the condition, all subsequent steps are bypassed:
// COND=(16,LE)
This means: "If 16 is less than or equal to any step's return code (i.e., if any step returns more than 16), bypass remaining steps." We will cover COND in depth in the conditional execution section.
Complete JOB Statement Example
//PAYROLL JOB (ACCT001,'DEPT-42'),
// 'JANE SMITH',
// CLASS=A,
// MSGCLASS=X,
// MSGLEVEL=(1,1),
// NOTIFY=&SYSUID,
// REGION=0M,
// TIME=1440
See code/example-01-basic-job.jcl for a complete job with all statement types.
The EXEC Statement
The EXEC statement identifies a program to execute or a procedure to invoke. Each EXEC statement defines one job step.
Executing a Program (PGM=)
//STEP010 EXEC PGM=PAYROLL,REGION=64M,TIME=5
The PGM= parameter names the load module to execute. The system searches for this module in the step's STEPLIB, the job's JOBLIB, or the system linklist (in that order).
Executing a Procedure (PROC=)
//STEP010 EXEC PROC=IGYWCLG
Or simply:
//STEP010 EXEC IGYWCLG
When neither PGM= nor PROC= is coded, the system assumes the operand is a procedure name.
PARM= (Passing Parameters to Programs)
The PARM= parameter passes a character string to the executing program. In COBOL, this string is received via the USING phrase on the PROCEDURE DIVISION header:
//STEP010 EXEC PGM=PAYROLL,PARM='2024/12/15'
In the COBOL program:
PROCEDURE DIVISION USING WS-PARM-DATA.
Where WS-PARM-DATA is defined with a 2-byte length prefix followed by the parameter data. The maximum PARM length is 100 characters.
COND= (Step-Level Condition)
The COND parameter on an EXEC statement tests return codes from previous steps to determine whether to bypass the current step. This is covered in detail in the conditional execution section.
REGION= and TIME= at Step Level
When coded on the EXEC statement, REGION and TIME apply to that specific step rather than the entire job:
//STEP020 EXEC PGM=RPTGEN,REGION=128M,TIME=10
The DD Statement: Defining Datasets
The DD (Data Definition) statement is the workhorse of JCL. It connects the logical file names used in your COBOL program to actual physical datasets on the system. Every SELECT statement in your COBOL program's FILE-CONTROL paragraph corresponds to a DD statement in the JCL.
For example, if your COBOL program contains:
SELECT EMPLOYEE-INPUT
ASSIGN TO EMPINPUT
Then your JCL must contain a DD statement named EMPINPUT:
//EMPINPUT DD DSN=PROD.EMPLOYEE.MASTER,DISP=SHR
DSN= (Dataset Name)
The DSN= (or DSNAME=) parameter specifies the name of the dataset. z/OS dataset names consist of one or more qualifiers separated by periods. Each qualifier can be up to 8 characters, and the total name cannot exceed 44 characters:
//EMPINPUT DD DSN=PROD.PAYROLL.EMPLOYEE.MASTER,DISP=SHR
Temporary datasets use the && prefix. They exist only for the duration of the job and are automatically deleted when the job completes:
//TEMPFILE DD DSN=&&SORTED,
// DISP=(NEW,PASS),
// SPACE=(TRK,(10,5)),
// DCB=(RECFM=FB,LRECL=80,BLKSIZE=0)
Referback allows you to refer to a dataset from a previous step without repeating its name. Use *.stepname.ddname:
//INPUT DD DSN=*.STEP010.TEMPFILE,DISP=(OLD,DELETE)
DISP= (Disposition)
The DISP parameter is arguably the most important DD parameter. It has three subparameters:
DISP=(status,normal-disposition,abnormal-disposition)
Status (first subparameter) -- the initial state of the dataset:
| Value | Meaning |
|---|---|
| NEW | Dataset does not exist; create it |
| OLD | Dataset exists; request exclusive access |
| SHR | Dataset exists; allow shared access |
| MOD | If cataloged, open for append; if not, treat as NEW |
Normal disposition (second subparameter) -- what to do if the step completes successfully:
| Value | Meaning |
|---|---|
| CATLG | Keep the dataset and add it to the catalog |
| KEEP | Keep the dataset (do not catalog) |
| DELETE | Delete the dataset |
| PASS | Pass the dataset to a subsequent step |
Abnormal disposition (third subparameter) -- what to do if the step abends:
| Value | Meaning |
|---|---|
| CATLG | Keep and catalog even on failure |
| KEEP | Keep even on failure |
| DELETE | Delete on failure |
Common patterns:
DISP=SHR Read an existing dataset (shared)
DISP=(NEW,CATLG,DELETE) Create new; catalog if OK, delete if abend
DISP=(OLD,KEEP,KEEP) Exclusive access; keep regardless
DISP=(NEW,PASS) Create temporary; pass to next step
DISP=(OLD,DELETE,KEEP) Delete if OK; keep if abend (for cleanup)
DISP=(MOD,CATLG,DELETE) Append to existing or create new
A critical rule to remember: if you code only DISP=NEW without the second subparameter, the dataset is deleted at the end of the step. Always code at least the first two subparameters for new datasets.
SPACE= (Space Allocation)
The SPACE parameter specifies how much disk space to allocate for a new dataset:
SPACE=(unit,(primary,secondary,directory),RLSE)
Unit: The allocation unit -- TRK (tracks), CYL (cylinders), or a block size in bytes.
Primary: The initial allocation.
Secondary: The amount for each additional extent (up to 15 extents on non-SMS volumes).
Directory: Number of 256-byte directory blocks (for PDS only).
RLSE: Release unused space when the dataset is closed.
Examples:
SPACE=(CYL,(5,2),RLSE) 5 cylinders primary, 2 secondary
SPACE=(TRK,(100,50)) 100 tracks primary, 50 secondary
SPACE=(CYL,(20,10,50)) PDS with 50 directory blocks
SPACE=(32000,(100,20),RLSE) Average block size allocation
One cylinder is approximately 849,960 bytes on a 3390 device. One track is approximately 56,664 bytes.
DCB= (Data Control Block)
The DCB parameter defines the physical characteristics of the dataset:
DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)
RECFM (Record Format):
| Value | Meaning |
|---|---|
| F | Fixed length, unblocked |
| FB | Fixed length, blocked |
| V | Variable length, unblocked |
| VB | Variable length, blocked |
| FBA | Fixed, blocked, with ASA print control |
| VBA | Variable, blocked, with ASA print control |
| U | Undefined length |
LRECL (Logical Record Length): The length of each record in bytes. For FB files, this is the exact record length. For VB files, this is the maximum record length including the 4-byte record descriptor word (RDW).
BLKSIZE (Block Size): The physical block size. Coding BLKSIZE=0 tells the system to calculate the optimal block size automatically, which is the recommended practice on modern z/OS.
For report files using ASA print control characters:
//RPTFILE DD SYSOUT=*,DCB=(RECFM=FBA,LRECL=133)
The extra byte (133 instead of 132) holds the carriage control character: ' ' (space) for single spacing, '0' for double spacing, '-' for triple spacing, '1' for new page, and '+' for overprint.
UNIT= and VOL=
UNIT specifies the device type for a new dataset. SYSDA is the generic name for any available disk volume:
UNIT=SYSDA
VOL (Volume) specifies a particular disk volume. This is rarely coded in modern SMS-managed environments:
VOL=SER=PROD01
SYSOUT= (Print Output)
SYSOUT directs output to the Job Entry Subsystem (JES) spool for printing or viewing:
//RPTFILE DD SYSOUT=*
SYSOUT=* uses the MSGCLASS from the JOB statement. You can also code a specific class:
//RPTFILE DD SYSOUT=A
Instream Data (* and DATA)
The DD * statement provides data directly within the JCL. The data follows the DD statement and ends with a /* delimiter or the next // statement:
//CONTROL DD *
PAY-PERIOD=BI-WEEKLY
DEDUCTION-TABLE=2024
REPORT-LEVEL=DETAIL
/*
If your instream data itself contains // in columns 1-2 or /*, use DD DATA instead:
//SYSIN DD DATA
// This line starts with // but is data, not JCL
/*
DUMMY (Null File)
DD DUMMY defines a file that your program can open, read, and write without error, but no actual I/O occurs. Reads return end-of-file immediately. This is useful for optional files:
//OPTFILE DD DUMMY
Concatenation
You can concatenate multiple datasets under a single DD name. The program sees them as one continuous file:
//SYSLIB DD DSN=DEV.COBOL.COPYLIB,DISP=SHR
// DD DSN=PROD.COBOL.COPYLIB,DISP=SHR
// DD DSN=SYS1.COBOL.COPYLIB,DISP=SHR
The subsequent DD statements have no name (they are "unnamed" and therefore concatenated with the preceding named DD). The system searches these in order, which is important for copybook libraries -- it finds the first match and uses it.
Procedures (PROCs)
Procedures allow you to define a set of JCL statements once and invoke them multiple times. This promotes reuse, reduces errors, and standardizes job streams.
Instream Procedures (PROC/PEND)
An instream procedure is defined within the JCL itself, using PROC and PEND:
//COBRUN PROC PGMNAME=,INPDSN=,OUTDSN=
//*
//RUN EXEC PGM=&PGMNAME,REGION=64M
//STEPLIB DD DSN=PROD.COBOL.LOADLIB,DISP=SHR
//INPUT DD DSN=&INPDSN,DISP=SHR
//OUTPUT DD DSN=&OUTDSN,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(5,2),RLSE),
// DCB=(RECFM=FB,LRECL=200,BLKSIZE=0),
// UNIT=SYSDA
//REPORT DD SYSOUT=*
//COBRUN PEND
To invoke the procedure:
//ACREC EXEC COBRUN,
// PGMNAME=ARPROC,
// INPDSN='PROD.AR.TRANSACTIONS',
// OUTDSN='PROD.AR.PROCESSED.D241215'
Cataloged Procedures
A cataloged procedure is stored as a member in a procedure library (PROCLIB). The JCLLIB statement tells JES where to find it:
//MYLIBS JCLLIB ORDER=(DEV.JCLLIB,SYS1.PROCLIB)
IBM supplies several cataloged procedures for COBOL:
| Procedure | Purpose |
|---|---|
| IGYWC | Compile only |
| IGYWCL | Compile and link-edit |
| IGYWCLG | Compile, link-edit, and go (execute) |
| IGYWCG | Compile and go (no permanent load module) |
| IGYWPL | Compile, prelink, and link-edit |
| IGYWPLG | Compile, prelink, link-edit, and go |
Symbolic Parameters
Symbolic parameters begin with & and are defined on the PROC statement with default values. When the procedure is invoked, actual values replace the symbols:
//COBRUN PROC PGMNAME=,RGNSZ=64M
//RUN EXEC PGM=&PGMNAME,REGION=&RGNSZ
Invocation with override:
//MYSTEP EXEC COBRUN,PGMNAME=PAYROLL,RGNSZ=128M
Overriding DD Statements in Procedures
To override a DD statement within a procedure, use the format stepname.ddname:
//MYSTEP EXEC IGYWCLG
//COBOL.SYSLIB DD DSN=MY.COPYLIB,DISP=SHR
//GO.EMPINPUT DD DSN=MY.TEST.DATA,DISP=SHR
When the procedure is invoked with a step name, the override reference is procstepname.stepname.ddname:
//CLG EXEC IGYWCLG
//CLG.COBOL.SYSLIB DD DSN=MY.COPYLIB,DISP=SHR
See code/example-04-procedures.jcl for comprehensive procedure examples.
Conditional Execution
Batch jobs often require conditional logic: skip a reporting step if the processing step failed, or abort the job if a critical validation finds errors. JCL provides two mechanisms for this.
The COND Parameter (Traditional)
The COND parameter tests return codes from previous steps to determine whether to bypass a step. This is the older approach and is still widely used in existing JCL.
Important conceptual point: COND specifies the condition under which a step is skipped. If the condition is true, the step does not execute. This inverted logic frequently confuses newcomers.
The syntax is:
COND=(code,operator,stepname)
Where operator is: GT, GE, EQ, LT, LE, NE.
The comparison is: code operator stepname's-return-code.
If this evaluates to true, the step is bypassed.
Examples:
| COND Parameter | Meaning | Step Runs When... |
|---|---|---|
COND=(0,NE,STEP010) |
Skip if 0 is not equal to STEP010's RC | STEP010 RC = 0 |
COND=(4,LT,STEP020) |
Skip if 4 is less than STEP020's RC | STEP020 RC <= 4 |
COND=(0,EQ,STEP010) |
Skip if 0 equals STEP010's RC | STEP010 RC != 0 |
COND=(8,LE,STEP010) |
Skip if 8 <= STEP010's RC | STEP010 RC < 8 |
Multiple conditions are ORed together:
//STEP030 EXEC PGM=RPTGEN,
// COND=((0,NE,STEP010),(4,LT,STEP020))
This means: bypass STEP030 if STEP010's RC is not 0 or if STEP020's RC is greater than 4.
To express "run only if all previous steps returned 0":
COND=(0,NE)
Without a stepname, the system checks all previous steps.
IF/THEN/ELSE/ENDIF (Modern Approach)
The IF/THEN/ELSE/ENDIF construct provides more readable conditional logic and is the recommended approach:
// IF (STEP010.RC = 0) THEN
//STEP020 EXEC PGM=PROCESS
//...
// ELSE
//NOTIFY EXEC PGM=SENDMSG
//...
// ENDIF
The IF statement supports compound conditions:
// IF (STEP010.RC = 0 & STEP020.RC <= 4) THEN
Operators available in IF statements:
| Operator | Meaning |
|---|---|
= or EQ |
Equal |
< or LT |
Less than |
> or GT |
Greater than |
<= or LE |
Less than or equal |
>= or GE |
Greater than or equal |
!= or NE |
Not equal |
& |
AND |
\| |
OR |
NOT |
Negation |
You can test for abnormal termination:
// IF (STEP010.ABEND) THEN
//...handle abend...
// ENDIF
Or test for specific abend codes:
// IF (STEP010.ABENDCC = S0C7) THEN
//...handle data exception...
// ENDIF
IF/THEN/ELSE/ENDIF can be nested:
// IF (STEP010.RC = 0) THEN
//STEP020 EXEC PGM=PROCESS
// IF (STEP020.RC <= 4) THEN
//STEP030 EXEC PGM=REPORT
// ELSE
//ERRNOTFY EXEC PGM=SENDMSG
// ENDIF
// ELSE
//SRTNOTFY EXEC PGM=SENDMSG
// ENDIF
See code/example-03-multi-step.jcl for complete examples of both approaches.
Dataset Types
z/OS supports several dataset organizations that you will encounter as a COBOL programmer:
Sequential (PS - Physical Sequential)
The simplest and most common type. Records are stored and accessed in order. Most COBOL batch files are sequential.
//EMPFILE DD DSN=PROD.EMPLOYEE.MASTER,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(10,5)),
// DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)
Partitioned Dataset (PDS/PDSE)
A PDS is a library containing multiple members, each addressed by an 8-character name. Source code, copybooks, load modules, and JCL are typically stored in PDSes.
//SRCLIB DD DSN=DEV.COBOL.SOURCE,DISP=SHR The whole PDS
//SRCMBR DD DSN=DEV.COBOL.SOURCE(PAYROLL),DISP=SHR One member
A PDSE (Partitioned Dataset Extended) is the modern replacement for a PDS. It supports longer member names, does not require compression, and handles concurrent updates better. You create a PDSE by adding DSNTYPE=LIBRARY:
//NEWPDSE DD DSN=PROD.COBOL.SOURCE,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(20,10,50)),
// DSNTYPE=LIBRARY
VSAM (Virtual Storage Access Method)
VSAM datasets support keyed, relative record, and entry-sequenced access. They are defined and managed using IDCAMS (Access Method Services) rather than JCL DD parameters:
- KSDS (Key Sequenced Data Set): Records accessed by a key field. Used for master files.
- ESDS (Entry Sequenced Data Set): Records stored in entry order. Used for logs.
- RRDS (Relative Record Data Set): Records accessed by relative record number.
- LDS (Linear Data Set): Byte-stream access. Used by DB2.
VSAM datasets in JCL require only the DSN and DISP (the DCB information comes from the catalog):
//CUSTMAST DD DSN=PROD.CUSTOMER.KSDS,DISP=SHR
Generation Data Groups (GDG)
A GDG is a collection of historically related sequential datasets (generations) managed under a single base name. They are ideal for files that are created periodically, such as daily transaction files or weekly payroll runs.
Defining a GDG base:
//DEFGDG EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DEFINE GDG -
(NAME(PROD.PAYROLL.HISTORY) -
LIMIT(30) -
NOEMPTY -
SCRATCH)
/*
- LIMIT: Maximum number of generations to retain.
- NOEMPTY: When the limit is reached, only the oldest generation is removed (versus EMPTY, which removes all).
- SCRATCH: Physically delete the dataset when it is uncataloged.
Creating a new generation:
//PAYFILE DD DSN=PROD.PAYROLL.HISTORY(+1),
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(10,5),RLSE),
// DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)
Referencing generations by relative number:
//CURRENT DD DSN=PROD.PAYROLL.HISTORY(0),DISP=SHR Current
//PREVIOUS DD DSN=PROD.PAYROLL.HISTORY(-1),DISP=SHR Previous
//TWOBACK DD DSN=PROD.PAYROLL.HISTORY(-2),DISP=SHR Two back
Relative generation numbers are resolved at job start time, not at step execution time. This means that if your job creates (+1) in step 1 and reads (0) in step 2, step 2 reads the generation that was current before the job started, not the one just created. To read the newly created generation, use a referback: DSN=*.STEP010.PAYFILE.
See code/example-06-gdg.jcl for comprehensive GDG examples.
Common Utilities
Every COBOL programmer works regularly with a core set of z/OS utility programs. These utilities are invoked through JCL just like any other program.
IEBGENER: Copy and Convert Sequential Datasets
IEBGENER is the simplest copy utility. It copies a sequential dataset or PDS member from SYSUT1 to SYSUT2:
//COPY EXEC PGM=IEBGENER
//SYSPRINT DD SYSOUT=*
//SYSIN DD DUMMY
//SYSUT1 DD DSN=PROD.INPUT.FILE,DISP=SHR
//SYSUT2 DD DSN=PROD.OUTPUT.FILE,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(10,5)),
// DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)
When SYSIN DD DUMMY is coded, IEBGENER performs a straight copy. You can also use SYSIN control statements for field selection and reformatting.
IEBCOPY: PDS Copy and Compress
IEBCOPY operates on partitioned datasets. Its most common uses:
Copy selected members:
//PDSCOPY EXEC PGM=IEBCOPY
//SYSPRINT DD SYSOUT=*
//INLIB DD DSN=DEV.COBOL.SOURCE,DISP=SHR
//OUTLIB DD DSN=PROD.COBOL.SOURCE,DISP=SHR
//SYSUT3 DD SPACE=(CYL,(5,5)),UNIT=SYSDA
//SYSUT4 DD SPACE=(CYL,(5,5)),UNIT=SYSDA
//SYSIN DD *
COPY OUTDD=OUTLIB,INDD=INLIB
SELECT MEMBER=(PAYROLL,ARPROC,APPROC)
/*
Compress a PDS in place (reclaim space from deleted members):
//COMPRESS EXEC PGM=IEBCOPY
//SYSPRINT DD SYSOUT=*
//MYPDS DD DSN=DEV.COBOL.SOURCE,DISP=OLD
//SYSUT3 DD SPACE=(CYL,(5,5)),UNIT=SYSDA
//SYSUT4 DD SPACE=(CYL,(5,5)),UNIT=SYSDA
//SYSIN DD *
COPY OUTDD=MYPDS,INDD=MYPDS
/*
Note: PDSE (DSNTYPE=LIBRARY) datasets do not require compression.
IDCAMS: Access Method Services
IDCAMS is the Swiss Army knife of z/OS utilities. It manages VSAM datasets, catalogs, and more:
DEFINE CLUSTER -- create a VSAM KSDS:
DEFINE CLUSTER -
(NAME(PROD.CUSTOMER.KSDS) -
INDEXED -
RECORDS(50000 10000) -
RECORDSIZE(350 350) -
FREESPACE(20 10) -
SHAREOPTIONS(2 3) -
SPEED) -
DATA -
(NAME(PROD.CUSTOMER.KSDS.DATA) -
CONTROLINTERVALSIZE(4096) -
KEYS(10 0)) -
INDEX -
(NAME(PROD.CUSTOMER.KSDS.INDEX))
REPRO -- copy data into or out of a VSAM dataset:
REPRO INFILE(SEQFILE) OUTFILE(VSAMFILE) REPLACE
PRINT -- display dataset contents:
PRINT INFILE(VSAMFILE) CHARACTER COUNT(100)
LISTCAT -- display catalog information:
LISTCAT ENTRIES(PROD.CUSTOMER.KSDS) ALL
DELETE -- delete a dataset:
DELETE PROD.CUSTOMER.KSDS CLUSTER PURGE
A useful IDCAMS technique is SET MAXCC = 0 to reset the condition code after an expected error (such as deleting a dataset that may not exist):
DELETE PROD.TEMP.FILE NONVSAM PURGE
SET MAXCC = 0
DFSORT (ICEMAN/SORT): Sort, Merge, and Copy
DFSORT is the high-performance sort utility. It can sort, merge, copy, select, and reformat records:
//SORTJOB EXEC PGM=SORT
//SYSOUT DD SYSOUT=*
//SORTIN DD DSN=PROD.TRANS.FILE,DISP=SHR
//SORTOUT DD DSN=PROD.TRANS.SORTED,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(10,5)),
// DCB=(RECFM=FB,LRECL=150,BLKSIZE=0)
//SYSIN DD *
SORT FIELDS=(1,10,CH,A,11,8,CH,D)
INCLUDE COND=(145,1,CH,EQ,C'A')
/*
The SORT control statement syntax: SORT FIELDS=(position,length,format,order,...) where format is CH (character), ZD (zoned decimal), PD (packed decimal), BI (binary), etc., and order is A (ascending) or D (descending).
ICETOOL extends DFSORT with multiple operations in a single step:
//ICETOOL EXEC PGM=ICETOOL
//TOOLMSG DD SYSOUT=*
//DFSMSG DD SYSOUT=*
//INDD DD DSN=PROD.DATA.FILE,DISP=SHR
//OUTDD DD ...
//TOOLIN DD *
COPY FROM(INDD) TO(OUTDD) USING(CTL1)
STATS FROM(INDD) ON(50,10,ZD)
/*
//CTL1CNTL DD *
INCLUDE COND=(50,10,ZD,GT,+0000010000)
/*
IEFBR14: The Do-Nothing Program
IEFBR14 does absolutely nothing -- it simply returns a condition code of 0. Its purpose is to allow dataset allocation and deletion through JCL without running an actual program:
//* Allocate an empty dataset
//ALLOC EXEC PGM=IEFBR14
//NEWFILE DD DSN=PROD.NEW.DATASET,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(10,5)),
// DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)
//* Delete an existing dataset
//DELETE EXEC PGM=IEFBR14
//OLDFILE DD DSN=PROD.OLD.DATASET,
// DISP=(OLD,DELETE,DELETE)
See code/example-05-utilities.jcl for comprehensive utility examples.
JCL for COBOL Development
The complete lifecycle of a COBOL program on z/OS involves several JCL-driven steps.
The Compile Step
The Enterprise COBOL compiler is invoked as program IGYCRCTL:
//COMPILE EXEC PGM=IGYCRCTL,
// PARM='SOURCE,LIST,MAP,XREF,APOST,RENT,OFFSET'
//STEPLIB DD DSN=IGY.V6R4M0.SIGYCOMP,DISP=SHR
//SYSIN DD DSN=DEV.COBOL.SOURCE(PAYROLL),DISP=SHR
//SYSLIB DD DSN=DEV.COBOL.COPYLIB,DISP=SHR
//SYSPRINT DD SYSOUT=*
//SYSLIN DD DSN=&&OBJMOD,DISP=(MOD,PASS),
// SPACE=(TRK,(10,5)),
// DCB=(RECFM=FB,LRECL=80,BLKSIZE=3200)
//SYSUT1 DD SPACE=(CYL,(1,1)),UNIT=SYSDA
//SYSUT2 DD SPACE=(CYL,(1,1)),UNIT=SYSDA
//SYSUT3 DD SPACE=(CYL,(1,1)),UNIT=SYSDA
//SYSUT4 DD SPACE=(CYL,(1,1)),UNIT=SYSDA
//SYSUT5 DD SPACE=(CYL,(1,1)),UNIT=SYSDA
//SYSUT6 DD SPACE=(CYL,(1,1)),UNIT=SYSDA
//SYSUT7 DD SPACE=(CYL,(1,1)),UNIT=SYSDA
//SYSMDECK DD SPACE=(CYL,(1,1)),UNIT=SYSDA
Key DD names for the compiler:
| DD Name | Purpose |
|---|---|
| SYSIN | COBOL source code input |
| SYSLIB | Copybook libraries (for COPY statements) |
| SYSPRINT | Compiler listing |
| SYSLIN | Object module output |
| SYSUT1-SYSUT7 | Compiler work files |
| SYSMDECK | Macro definitions |
Common compiler options (coded on PARM=):
| Option | Purpose |
|---|---|
| SOURCE | Include source listing |
| LIST | Include object code listing |
| MAP | Include data division map |
| XREF | Include cross-reference listing |
| APOST | Use apostrophe as literal delimiter |
| RENT | Generate reentrant code |
| OFFSET | Include condensed listing with offset addresses |
| OPT(0/1/2) | Optimization level |
| TEST | Generate debugging information |
| NOTEST | No debugging information |
Compiler return codes:
| RC | Meaning |
|---|---|
| 0 | Successful compilation, no errors |
| 4 | Warnings (W-level) -- program will likely execute correctly |
| 8 | Errors (E-level) -- object code produced but may not run correctly |
| 12 | Severe errors (S-level) -- no object code produced |
| 16 | Unrecoverable errors (U-level) -- compilation terminated |
The Link-Edit Step
The linkage editor (IEWL or HEWL) combines object modules into an executable load module:
//LKED EXEC PGM=IEWL,
// COND=(8,LT,COMPILE),
// PARM='LIST,MAP,RENT,XREF'
//SYSLIB DD DSN=CEE.SCEELKED,DISP=SHR
//SYSLIN DD DSN=&&OBJMOD,DISP=(OLD,DELETE)
// DD *
NAME PAYROLL(R)
/*
//SYSLMOD DD DSN=DEV.COBOL.LOADLIB(PAYROLL),DISP=SHR
//SYSPRINT DD SYSOUT=*
//SYSUT1 DD SPACE=(CYL,(1,1)),UNIT=SYSDA
The COND=(8,LT,COMPILE) means: skip the link step if 8 is less than the compile step's return code -- that is, if the compiler returned 12 or higher.
The CEE.SCEELKED library contains the Language Environment runtime routines that are automatically called by COBOL programs.
The Go Step
The go step executes the linked program:
//GO EXEC PGM=PAYROLL,
// COND=((8,LT,COMPILE),(4,LT,LKED)),
// REGION=64M,
// PARM='2024/12/15'
//STEPLIB DD DSN=DEV.COBOL.LOADLIB,DISP=SHR
//EMPINPUT DD DSN=TEST.EMPLOYEE.MASTER,DISP=SHR
//PAYFILE DD DSN=TEST.PAYROLL.OUTPUT,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(1,1),RLSE),
// DCB=(RECFM=FB,LRECL=200,BLKSIZE=0)
//RPTFILE DD SYSOUT=*
Using IGYWCLG (The Easy Way)
For development and testing, the IBM-supplied IGYWCLG procedure handles all three steps:
//STEP01 EXEC IGYWCLG,
// PARM.COBOL='SOURCE,LIST,MAP,XREF,APOST,RENT',
// PARM.LKED='LIST,MAP,RENT',
// PARM.GO='2024/12/15'
//COBOL.SYSIN DD DSN=DEV.COBOL.SOURCE(PAYROLL),DISP=SHR
//COBOL.SYSLIB DD DSN=DEV.COBOL.COPYLIB,DISP=SHR
//LKED.SYSLMOD DD DSN=DEV.COBOL.LOADLIB(PAYROLL),DISP=SHR
//GO.EMPINPUT DD DSN=TEST.EMPLOYEE.MASTER,DISP=SHR
//GO.RPTFILE DD SYSOUT=*
See code/example-02-compile-link-go.jcl for all three approaches.
DB2 Precompile and Bind Steps
For COBOL programs that access DB2, two additional steps precede compilation:
//* DB2 PRECOMPILE
//PRECOMP EXEC PGM=DSNHPC,
// PARM='HOST(COBOL),APOST,SOURCE'
//STEPLIB DD DSN=DSN.V13R1.SDSNLOAD,DISP=SHR
//SYSIN DD DSN=DEV.COBOL.SOURCE(PAYROLL),DISP=SHR
//SYSLIB DD DSN=DEV.COBOL.COPYLIB,DISP=SHR
// DD DSN=DEV.DB2.DCLGEN,DISP=SHR
//SYSPRINT DD SYSOUT=*
//SYSCIN DD DSN=&&MODSRC,DISP=(NEW,PASS),
// SPACE=(TRK,(10,5)),
// DCB=(RECFM=FB,LRECL=80,BLKSIZE=3200)
//DBRMLIB DD DSN=DEV.DB2.DBRMLIB(PAYROLL),DISP=SHR
After compilation and link-edit, a BIND step connects the DBRM (Database Request Module) to a DB2 plan or package:
//* DB2 BIND
//BIND EXEC PGM=IKJEFT01
//STEPLIB DD DSN=DSN.V13R1.SDSNLOAD,DISP=SHR
//DBRMLIB DD DSN=DEV.DB2.DBRMLIB,DISP=SHR
//SYSPRINT DD SYSOUT=*
//SYSTSPRT DD SYSOUT=*
//SYSTSIN DD *
DSN SYSTEM(DB2P)
BIND PACKAGE(PAYPACK) -
MEMBER(PAYROLL) -
ACTION(REPLACE) -
ISOLATION(CS) -
VALIDATE(BIND)
END
/*
CICS Translation Step
For COBOL programs that run under CICS, a translate step precedes compilation:
//* CICS TRANSLATE
//TRANSLATE EXEC PGM=DFHECP1$,
// PARM='COBOL3,SP,SOURCE'
//STEPLIB DD DSN=CICSTS.V5R6.SDFHLOAD,DISP=SHR
//SYSIN DD DSN=DEV.COBOL.SOURCE(CICSMAP),DISP=SHR
//SYSPRINT DD SYSOUT=*
//SYSPUNCH DD DSN=&&MODSRC,DISP=(NEW,PASS),
// SPACE=(TRK,(10,5)),
// DCB=(RECFM=FB,LRECL=80,BLKSIZE=3200)
The translated source is then compiled with additional CICS libraries in SYSLIB.
Common JCL Errors and Diagnosis
JCL Errors
JCL errors are detected before execution begins. Common causes:
| Error | Cause |
|---|---|
| IEFC001I | Statement does not begin with // |
| IEFC605I | Unidentified operation field |
| IEFC621I | Unidentified keyword parameter |
| IEFC630I | Unbalanced parentheses |
| IEFC632I | Incorrect DISP parameter |
| IEFC642I | Continuation error |
| IEFC652I | Dataset name too long (> 44 characters) |
ABEND Codes
When a program terminates abnormally, the system issues an ABEND code. System abends begin with S (S0C7, S0CB, S806, etc.), and user abends begin with U (U0100, U4038, etc.).
Common system abend codes for COBOL programmers:
| Code | Meaning | Common Cause |
|---|---|---|
| S0C1 | Operation exception | Invalid branch, corrupted storage |
| S0C4 | Protection exception | Accessing storage outside your region, subscript out of range |
| S0C7 | Data exception | Non-numeric data in a numeric field (the most common COBOL abend) |
| S0CB | Divide exception | Division by zero |
| S013 | Dataset open error | DD statement missing, wrong DCB attributes |
| S0D37 | Dataset space exhausted | Ran out of primary and secondary extents |
| S322 | CPU time limit exceeded | Infinite loop, or TIME too small |
| S806 | Module not found | Program not in STEPLIB/JOBLIB/linklist |
| S837 | Dataset space exhausted | All extents used |
| S913 | RACF security violation | No access authority to the dataset |
| SB37 | Dataset space exhausted | No space on volume for secondary allocation |
Common user abend codes:
| Code | Meaning |
|---|---|
| U0016 | COBOL SORT error |
| U1026 | COBOL OPEN error (file status other than 00) |
| U1037 | COBOL READ error |
| U4038 | Language Environment error |
Diagnosing Problems
When a job fails, check the following (in order):
- Job log: Look at the JCL messages for JCL errors.
- System messages: Look for IEFxxxI messages about allocation failures.
- SYSOUT output: Check your program's DISPLAY output and any error messages.
- Condition codes: Check each step's return code in the job log.
- Abend information: If an abend occurred, note the code and the offset (address).
- Compiler listing: Use the offset from the abend to find the failing COBOL statement in the compiler listing.
JES2/JES3 Job Processing
The Job Entry Subsystem (JES) manages the lifecycle of every job on z/OS. Most installations use JES2. A job goes through five phases:
-
Input: JES reads the JCL and assigns a job number (e.g., JOB12345). The JCL is stored on the JES spool.
-
Conversion: JES validates the JCL syntax, resolves symbolic parameters, expands procedures, and converts the JCL into internal control blocks. JCL errors are detected here.
-
Execution: The job is placed in a priority queue and waits for an initiator. When an initiator is available, it allocates the datasets for each step and executes the programs. Return codes are set by each step.
-
Output: Spool datasets (SYSOUT) are processed for printing, viewing, or routing. You can view output using SDSF or your installation's output management tool.
-
Purge: After output is processed, the job is removed from the JES spool. The JESLOG and all spool datasets are deleted.
SDSF (System Display and Search Facility)
SDSF is the primary tool for viewing job status and output on z/OS:
- ST (Status): Shows active and queued jobs
- O (Output): Shows jobs with pending output
- H (Held): Shows held output
- LOG: Shows the system log
Common SDSF commands:
| Command | Purpose |
|---|---|
PREFIX jobname |
Filter by job name |
OWNER userid |
Filter by submitting user |
S (next to job) |
View job output (DDNAME list) |
? (next to DD) |
Browse the DD output |
P (next to job) |
Purge the job |
C (next to job) |
Cancel a running job |
SJ (next to job) |
View the JCL |
Best Practices
Naming Conventions
- Job names: Use a consistent pattern that identifies the application and purpose. For example:
MBPAY01(Metro Banking, Payroll, job 01). - Step names: Use numbered step names (
STEP010,STEP020, etc.) with increments of 10, leaving room for insertions. - Dataset names: Follow your installation's naming standards. A typical pattern:
HLQ.APPLICATION.TYPE.QUALIFIER(e.g.,PROD.PAYROLL.EMPLOYEE.MASTER).
Documentation
Use comment statements (//*) liberally:
//*------------------------------------------------------------*
//* STEP020 - VALIDATE AND PROCESS TRANSACTIONS *
//* INPUT: Sorted transaction file from STEP010 *
//* OUTPUT: Valid transactions, reject file, error report *
//* RC=0: All records valid *
//* RC=4: Some records rejected (see reject file) *
//* RC=8+: Processing error *
//*------------------------------------------------------------*
DISP Coding
- Always code all three DISP subparameters for new datasets:
DISP=(NEW,CATLG,DELETE). - Use
DISP=SHRfor input files unless you need exclusive access. - Use
DISP=(OLD,DELETE,KEEP)for cleanup steps -- delete on success, keep on failure for diagnosis. - Never code
DISP=NEWwithout the second subparameter (the default normal disposition for NEW is DELETE).
Space Management
- Use
BLKSIZE=0to let the system calculate the optimal block size. - Code
RLSEon SPACE to release unused space. - Base space estimates on expected data volumes, adding a generous secondary allocation.
- Use
CYLfor large files,TRKfor small files.
Error Handling
- Use IF/THEN/ELSE/ENDIF for readable conditional logic.
- Check return codes from every critical step.
- Include notification steps that alert operations when critical steps fail.
- Design jobs for restartability -- use cleanup steps at the beginning to delete prior output.
- Use
SET MAXCC = 0in IDCAMS after expected "not found" conditions.
Procedure Usage
- Use procedures for repetitive tasks.
- Provide sensible default values for symbolic parameters.
- Document every symbolic parameter and its purpose.
- Use JCLLIB to control which procedure libraries are searched.
Performance
- For sort steps, specify adequate REGION (SORT can use available storage for internal sorting).
- Avoid unnecessary dataset copies; pass temporary datasets between steps.
- Use
DISP=(NEW,PASS)for temporary datasets rather thanDISP=(NEW,CATLG,DELETE)followed by a cleanup step.
The COBOL-JCL Connection: Tying It All Together
Let us trace the connection between a COBOL program and its JCL. Consider the EMPRPT program in code/example-07-cobol-program.cob:
In the COBOL program:
FILE-CONTROL.
SELECT EMPLOYEE-INPUT
ASSIGN TO EMPINPUT
SELECT EMPLOYEE-OUTPUT
ASSIGN TO EMPOUT
SELECT REJECT-FILE
ASSIGN TO EMPREJECT
SELECT REPORT-FILE
ASSIGN TO RPTFILE
SELECT PARM-FILE
ASSIGN TO PARMFILE
Each ASSIGN TO name becomes a DD name in the JCL:
//STEP010 EXEC PGM=EMPRPT,REGION=64M
//STEPLIB DD DSN=PROD.COBOL.LOADLIB,DISP=SHR
//EMPINPUT DD DSN=PROD.EMPLOYEE.MASTER,DISP=SHR
//EMPOUT DD DSN=PROD.EMPLOYEE.VALID,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(5,2),RLSE),
// DCB=(RECFM=FB,LRECL=200,BLKSIZE=0),
// UNIT=SYSDA
//EMPREJECT DD DSN=PROD.EMPLOYEE.REJECTS,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(CYL,(1,1),RLSE),
// DCB=(RECFM=FB,LRECL=250,BLKSIZE=0),
// UNIT=SYSDA
//RPTFILE DD SYSOUT=*,DCB=(RECFM=FBA,LRECL=133)
//PARMFILE DD *
RPT-TYPE=DETAIL
MIN-SALARY=025000
DEPT-FILTER=ALL
/*
The COBOL FD (File Description) specifies the record layout and length. The JCL DD statement specifies the physical dataset characteristics. These must be consistent:
| COBOL FD | JCL DCB | Must Match |
|---|---|---|
RECORD CONTAINS 200 CHARACTERS |
LRECL=200 |
Yes |
RECORDING MODE IS F |
RECFM=FB |
Format type |
BLOCK CONTAINS 0 RECORDS |
BLKSIZE=0 |
System-determined |
The RECORDING MODE IS F in COBOL corresponds to RECFM=F or RECFM=FB in JCL. The "B" (blocked) is a physical attribute that the COBOL program does not need to know about -- the system handles blocking and deblocking transparently.
Summary
JCL is the essential bridge between your COBOL programs and the z/OS operating system. In this chapter, you learned:
- JOB statements define a job and its attributes (CLASS, MSGCLASS, NOTIFY, REGION, TIME).
- EXEC statements invoke programs (PGM=) or procedures (PROC=), pass parameters (PARM=), and control conditional execution (COND, IF/THEN/ELSE/ENDIF).
- DD statements connect COBOL file names to physical datasets, specifying name (DSN), access mode (DISP), space allocation (SPACE), and record format (DCB).
- Procedures encapsulate reusable JCL with symbolic parameters, and IBM supplies standard procedures (IGYWC, IGYWCL, IGYWCLG) for COBOL compilation.
- Conditional execution uses either the traditional COND parameter (which tests conditions to bypass a step) or the modern IF/THEN/ELSE/ENDIF construct.
- Dataset types include sequential (PS), partitioned (PDS/PDSE), VSAM, and GDG.
- Utility programs (IEBGENER, IEBCOPY, IDCAMS, DFSORT, IEFBR14) are everyday tools for COBOL programmers.
- COBOL development JCL involves compile, link-edit, and go steps, with additional steps for DB2 and CICS programs.
- Error diagnosis starts with the job log, ABEND codes, and compiler listings.
- JES manages the complete job lifecycle from submission through execution to output.
Mastering JCL may seem daunting at first, but it is a finite and logical system. Once you understand the three core statements (JOB, EXEC, DD) and their key parameters, everything else is built on that foundation. The exercises and case studies that follow will give you the hands-on practice needed to write JCL with confidence.