JCL Tutorial for Beginners: Complete Guide to Job Control Language (2026)
Master IBM JCL (Job Control Language) from scratch. This complete tutorial covers JOB, EXEC, and DD statements, DISP parameters, GDGs, procedures, VSAM, utilities, and real-world examples for z/OS mainframe developers.

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.
