JCL Tutorial for Beginners: Complete Guide to Job Control Language (2026)

JCL Tutorial: Complete Guide to Job Control Language
Job Control Language (JCL) is the scripting language used to submit and manage batch jobs on IBM mainframe systems running z/OS. Despite being over 50 years old, JCL remains essential in finance, insurance, healthcare, and government — industries where mainframes process hundreds of billions of transactions every day.
This tutorial covers everything you need to write real JCL from scratch: the three core statement types, data set handling, procedures, utilities, and error handling.
What is JCL?
JCL is the interface between you and z/OS. When you want to run a COBOL program, sort a file, or copy a data set on a mainframe, you don't type commands into a shell — you submit a JCL job. The z/OS Job Entry Subsystem (JES2 or JES3) receives the job, schedules it according to priority and resource availability, and runs it.
A JCL job is made up of statements that answer three questions:
- Who is submitting this job and how should it be run? (JOB statement)
- Which program should be executed? (EXEC statement)
- What data does that program need? (DD statements)
JCL Statement Format
Every JCL statement follows a rigid positional format. Columns matter.
//name operation operands comments
//STEP1 EXEC PGM=IEBGENER| Column | Content |
|---|---|
| 1–2 | // (identifies a JCL statement) |
| 3–10 | Name field (optional; must start in column 3 if present) |
| 12+ | Operation keyword (JOB, EXEC, DD, etc.) |
| After operation | Operands, separated by commas |
| After operands | Comments (separated by at least one space) |
A statement beginning with //* is a comment. A statement beginning with /* is a delimiter statement.
The JOB Statement
The JOB statement is always the first statement in a job. It identifies the job to z/OS and specifies execution parameters.
//MYJOB JOB (ACCT001),'MY JOB NAME',
// CLASS=A,
// MSGCLASS=X,
// MSGLEVEL=(1,1),
// NOTIFY=&SYSUID,
// REGION=0MKey JOB Parameters
| Parameter | Description |
|---|---|
(accounting-info) | First positional — accounting/billing information for the installation |
'programmer-name' | Second positional — free-text identifier (up to 20 characters) |
CLASS= | Job class — controls which initiator (execution environment) picks up the job |
MSGCLASS= | Sysout class — where job output (JES log, SYSPRINT) is directed |
MSGLEVEL=(stmts,msgs) | Controls how much JCL and system messages appear in the job log |
NOTIFY=&SYSUID | Sends a TSO message to the submitter when the job ends |
REGION=0M | Maximum virtual memory — 0M means use the system maximum |
TIME=(mins,secs) | CPU time limit before the job is cancelled |
The EXEC Statement
The EXEC statement defines a job step — one unit of work. A job can contain multiple steps; each step runs a program or procedure.
EXEC PGM= (Running a Program Directly)
//STEP1 EXEC PGM=IEBGENERPGM= specifies the load module name exactly as it appears in the system's program library (LINKLIST or STEPLIB).
EXEC PROC= (Invoking a Procedure)
//STEP1 EXEC PROC=MYPROC
//STEP1 EXEC MYPROC (shorthand — PROC= is assumed)Key EXEC Parameters
| Parameter | Description |
|---|---|
PGM=name | Run this load module |
PROC=name | Execute this catalogued procedure |
COND= | Condition code test — skip or run this step depending on prior return codes |
PARM= | Pass a string of up to 100 characters to the program at runtime |
REGION= | Override the JOB-level region size for this step only |
TIME= | CPU time limit for this step |
Understanding COND=
//STEP2 EXEC PGM=MYPROG,COND=(4,LT,STEP1)COND=(4,LT,STEP1) means: skip this step if 4 is less than the return code of STEP1 — i.e., run STEP2 only if STEP1 completed with return code ≥ 4. The condition is tested from the perspective of the comparison operand.
Common interpretations:
COND=(0,NE)— skip if any prior step returned non-zero (skip on any error)COND=(4,LT)— skip if any prior step returned a code > 4
The DD Statement
DD (Data Definition) statements describe every data set a program reads or writes. Each DD in a step has a name that matches a //ddname reference inside the program's code.
//INPUT1 DD DSN=MY.INPUT.FILE,
// DISP=(OLD,KEEP,KEEP),
// UNIT=SYSDA,
// SPACE=(TRK,(10,5))DSN — Data Set Name
DSN= (or DSNAME=) specifies the fully-qualified name of the data set. On z/OS, data set names are hierarchical, separated by dots, up to 44 characters.
DSN=PROD.PAYROLL.MASTER
DSN=&&TEMPFILE (temporary data set — double ampersand)
DSN=*.STEP1.MYDATA (backward reference — use data set from STEP1)DISP — Disposition
DISP is one of the most important DD parameters. It has up to three sub-parameters:
DISP=(status, normal-termination, abnormal-termination)Status values:
| Value | Meaning |
|---|---|
NEW | Create a new data set |
OLD | Data set exists; request exclusive access |
SHR | Data set exists; allow shared read access |
MOD | Extend an existing sequential data set (or create if not found) |
Disposition values (normal and abnormal):
| Value | Meaning |
|---|---|
KEEP | Retain the data set after the step |
DELETE | Delete the data set after the step |
CATLG | Catalogue the data set (register it with the system catalog) |
UNCATLG | Remove from catalog but keep on disk |
PASS | Pass to the next step in the same job |
Common DISP combinations:
DISP=SHR (existing catalogued data set, shared access)
DISP=(NEW,CATLG,DELETE) (create, keep on success, delete on failure)
DISP=(OLD,DELETE) (exclusive access, delete after step)
DISP=(NEW,PASS,DELETE) (temporary — pass to next step)
DISP=(MOD,CATLG,CATLG) (append to existing sequential data set)UNIT and SPACE
UNIT=SYSDA (generic disk unit)
SPACE=(CYL,(5,2),RLSE) (5 cylinders primary, 2 secondary, release unused)
SPACE=(TRK,(100,50)) (100 tracks primary, 50 secondary)
SPACE=(80,(1000,500),RLSE) (block size 80, 1000 blocks primary)DCB — Data Control Block
DCB describes the physical characteristics of the data set:
DCB=(RECFM=FB,LRECL=80,BLKSIZE=8000)| Sub-parameter | Description |
|---|---|
RECFM=FB | Fixed Blocked records |
RECFM=VB | Variable Blocked records |
RECFM=U | Undefined record format |
LRECL=80 | Logical record length in bytes |
BLKSIZE=8000 | Physical block size |
Special DD Names
z/OS and most programs reserve certain DD names:
| DD Name | Purpose |
|---|---|
JOBLIB | Load library search — programs found here before LINKLIST |
STEPLIB | Step-level load library — overrides JOBLIB for one step |
SYSPRINT | Standard program output (messages, reports) |
SYSIN | Standard program input (control cards, SQL, parameters) |
SYSOUT | Sysout data set — output routed to a print class |
SYSUDUMP | Dump data set — receives storage dumps on abend |
SYSABEND | Similar to SYSUDUMP — formatted dump |
Sysout DD — Writing to Print
To direct output to the JES spool (for printing or viewing):
//SYSPRINT DD SYSOUT=* (same class as MSGCLASS)
//REPORT DD SYSOUT=A (class A — usually the default print class)
//HOLD DD SYSOUT=(A,INTRDR) (send to internal reader — submit as a job)In-Stream Data Sets
You can embed data directly inside JCL using DD * or DD DATA:
//SYSIN DD *
SORT FIELDS=(1,10,CH,A)
RECORD TYPE=F,LENGTH=80
/*DD * terminates at /* or at the next JCL statement. Use DD DATA,DLM=@@ if your in-stream data itself contains /*.
JCL Procedures
A procedure (PROC) is a reusable block of JCL steps. You define it once and invoke it from multiple jobs.
Catalogued Procedure
Stored in a system procedure library (SYS1.PROCLIB or a site-specific library):
//COBRUN PROC
//COMPILE EXEC PGM=IGYCRCTL,PARM='LIST,MAP'
//SYSPRINT DD SYSOUT=*
//SYSLIN DD DSN=&&LOADSET,DISP=(NEW,PASS),
// UNIT=SYSDA,SPACE=(TRK,(10,5))
//SYSIN DD DDNAME=COBOL
PENDInvoke it:
//STEP1 EXEC COBRUN
//COMPILE.COBOL DD DSN=MY.COBOL.SOURCE,DISP=SHRNote: COMPILE.COBOL overrides the COBOL DD inside the COMPILE step of the COBRUN procedure.
In-Stream Procedure
Defined within the job itself, between PROC and PEND:
//MYJOB JOB ...
//MYPROC PROC
//STEP1 EXEC PGM=MYPROG
//SYSPRINT DD SYSOUT=*
// PEND
//
//CALLSTEP EXEC MYPROCSymbolic Parameters
Procedures can accept parameters using &symbol notation:
//COPYPROC PROC INDSN=,OUTDSN=
//COPY EXEC PGM=IEBGENER
//SYSUT1 DD DSN=&INDSN,DISP=SHR
//SYSUT2 DD DSN=&OUTDSN,DISP=(NEW,CATLG,DELETE),
// UNIT=SYSDA,SPACE=(TRK,(10,5))
//SYSIN DD DUMMY
//SYSPRINT DD SYSOUT=*
PEND
//STEP1 EXEC COPYPROC,INDSN=PROD.INPUT,OUTDSN=PROD.OUTPUTGeneration Data Groups (GDGs)
A GDG is a series of related sequential data sets managed as a group. They're used for rolling backups, audit trails, and periodic processing (daily, weekly batch).
//OUTPUT DD DSN=MY.GDG(+1), (create new generation)
// DISP=(NEW,CATLG,DELETE),
// UNIT=SYSDA,
// SPACE=(TRK,(10,5)),
// DCB=MY.GDG.MODEL
//INPUT DD DSN=MY.GDG(0),DISP=SHR (current/latest generation)
//PREV DD DSN=MY.GDG(-1),DISP=SHR (previous generation)The GDG base must be created first (usually with IDCAMS DEFINE GDG).
Common JCL Utilities
z/OS ships with a set of utility programs for routine data management tasks:
IEBGENER — Copy Sequential Data Sets
//COPY EXEC PGM=IEBGENER
//SYSUT1 DD DSN=SOURCE.FILE,DISP=SHR
//SYSUT2 DD DSN=TARGET.FILE,DISP=(NEW,CATLG,DELETE),
// UNIT=SYSDA,SPACE=(TRK,(10,5))
//SYSIN DD DUMMY
//SYSPRINT DD SYSOUT=*IEFBR14 — Do Nothing (Allocate/Delete Data Sets)
The "do-nothing" program — used purely to allocate or delete data sets via DD statements:
//DELSTEP EXEC PGM=IEFBR14
//DELFILE DD DSN=OLD.FILE.TO.DELETE,
// DISP=(OLD,DELETE,DELETE)SORT (DFSORT or SyncSort) — Sort and Merge
//SORTJOB EXEC PGM=SORT
//SORTIN DD DSN=INPUT.FILE,DISP=SHR
//SORTOUT DD DSN=SORTED.FILE,DISP=(NEW,CATLG,DELETE),
// UNIT=SYSDA,SPACE=(CYL,(5,2))
//SYSIN DD *
SORT FIELDS=(1,10,CH,A,11,5,ZD,D)
RECORD TYPE=F,LENGTH=80
/*
//SYSOUT DD SYSOUT=*The SORT control statement above: sort ascending by bytes 1–10 (character), then descending by bytes 11–15 (zoned decimal).
IDCAMS — VSAM and Catalog Management
//VSAM EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DEFINE CLUSTER -
(NAME(MY.VSAM.KSDS) -
INDEXED -
KEYS(10 0) -
RECORDSIZE(200 200) -
TRACKS(50 10) -
VOLUMES(WORK01)) -
DATA -
(NAME(MY.VSAM.KSDS.DATA)) -
INDEX -
(NAME(MY.VSAM.KSDS.INDEX))
/*
//SYSOUT DD SYSOUT=*JCL Condition Codes and Return Codes
Every program that runs under z/OS returns a condition code (return code) between 0 and 4095. The conventions are:
| Return Code | Conventional Meaning |
|---|---|
0 | Successful — no errors |
4 | Warning — processing completed with minor issues |
8 | Error — results may be incomplete or unreliable |
12 | Severe error — processing stopped early |
16 | Terminal error — do not use the output |
Use COND= on later EXEC statements to skip steps based on prior return codes, ensuring a job doesn't run downstream steps against corrupted output.
JCL Abend Codes
An abend (abnormal end) occurs when a program crashes or is cancelled. Abend codes appear in the JES job log:
| Abend Type | Example | Meaning |
|---|---|---|
| System abend | S0C7 | Data exception — invalid data for operation (often bad numeric data) |
| System abend | S0C4 | Protection exception — program accessed storage it doesn't own |
| System abend | S322 | CPU time limit exceeded |
| System abend | S806 | Load module not found |
| System abend | S913 | Authorisation failure (security) |
| User abend | U1000 | User-defined abend in the application program |
JCL Restart and Checkpointing
To restart a failed job from a specific step (instead of re-running from the beginning):
//MYJOB JOB ...,RESTART=STEP3Or restart with step overrides:
//MYJOB JOB ...,RESTART=(STEP3.PROC1)For long-running jobs, COBOL programs can use CHKPT with DFSMS checkpointing to restart within a step.
VSAM Data Sets in JCL
VSAM (Virtual Storage Access Method) data sets are accessed differently from sequential files. They're catalogued and don't require UNIT or SPACE on the DD:
//VSAMFILE DD DSN=MY.KSDS.CLUSTER,DISP=SHR
//VSAMOUT DD DSN=MY.OUTPUT.KSDS,DISP=(NEW,CATLG,DELETE)VSAM data sets are created with IDCAMS (see above) and must be defined before use.
JCL Best Practices
Naming conventions:
- Use meaningful job and step names:
PAYROLL,SORTSTEP,RPTGEN - Prefix with your department or application:
//FINRPT01 JOB
Defensive coding:
- Always include
COND=tests to stop a job on failure rather than cascading errors downstream - Use
DISP=(NEW,CATLG,DELETE)— never leaveabnormal-dispblank; default is system-dependent - Specify
REGION=0Munless you have a specific reason to limit memory
Temporary data sets:
- Use
&&TEMPNAMEfor temporary files passed between steps — z/OS cleans them up at job end - Use
PASSdisposition to pass them between steps
Documentation:
- Add
//*comment statements before each step explaining its purpose - Keep procedures parameterised so they can be reused without modification
JCL Quick Reference Card
//jobname JOB (acct),'name',CLASS=,MSGCLASS=,MSGLEVEL=(1,1),NOTIFY=&SYSUID
//stepname EXEC PGM=program,PARM='value',COND=(cc,op,step)
//ddname DD DSN=dsname,DISP=(status,normal,abend),
// UNIT=SYSDA,SPACE=(TRK,(pri,sec)),
// DCB=(RECFM=FB,LRECL=80,BLKSIZE=8000)
// DD SYSOUT=*
// DD *
in-stream data
/*
//* Comment statementWhat to Learn Next
Once you're comfortable with core JCL, the natural progression is:
- COBOL — the most common language running under JCL on z/OS. See our COBOL Programming Tutorial for a complete guide.
- VSAM — the primary file system for mainframe applications. Understanding KSDS, ESDS, and RRDS data sets is essential for real-world work.
- JCL Interview Questions — if you're preparing for a mainframe developer interview, work through our 50 JCL Interview Questions.
- Mainframe Career — explore salaries and demand in our Mainframe Developer Salary Guide 2026.
