cheat sheet

JCL Quick Reference

Job Control Language syntax, common statements, EXEC, DD, and SYSOUT patterns.

#jcl#zos#mainframe#batchupdated 05-26-2026

JCL Quick Reference

What it is

JCL (Job Control Language) is the scripting language used to submit batch jobs on IBM mainframes running z/OS, unchanged in its core syntax since the System/360 era in the 1960s and maintained by IBM as part of the z/OS base. Every production batch workload on z/OS — whether a COBOL program, a sort utility, or a data copy — runs through a JCL job that specifies the program to execute, the input and output datasets, resource requirements, and dependencies between steps. Reach for JCL when you need to submit, schedule, or chain batch programs on z/OS; for interactive automation or glue code between steps, REXX is more flexible.

Basic job structure

Every JCL job follows a fixed three-card sequence: a JOB card that identifies the job to the system, one or more EXEC cards that name the program or procedure to run, and DD cards that describe the datasets each step reads or writes. Continuation lines must start with // in columns 1–2 and a non-blank character in column 4 or later.

text
//MYJOB    JOB (ACCT),'Alice Dev',CLASS=A,
//             MSGCLASS=X,MSGLEVEL=(1,1),
//             NOTIFY=&SYSUID
//*
//* Simple one-step job
//*
//STEP01   EXEC PGM=IEFBR14
//STEP02   EXEC PGM=IDCAMS
//SYSIN    DD *
  LISTCAT ENTRIES(MY.DATA.SET) ALL
/*
//SYSPRINT DD SYSOUT=*

Common statements

JOB statement

The JOB card is the first statement in every job and provides the job name, accounting information, and scheduling parameters. CLASS controls which initiator picks up the job, MSGCLASS sets where the job log is routed, and NOTIFY=&SYSUID sends a message to your TSO session when the job ends.

text
//jobname  JOB accounting-info,
//             programmer-name,
//             CLASS=job-class,
//             MSGCLASS=output-class,
//             MSGLEVEL=(statements,messages),
//             NOTIFY=&SYSUID,
//             REGION=0M,
//             TIME=1440

EXEC statement

PGM= runs a load module directly; PROC= (or just the proc name) expands a catalogued or in-stream procedure before running it. Each EXEC starts a new job step with its own return code; steps are numbered STEP01, STEP02, etc. by convention.

text
//stepname EXEC PGM=program-name
//stepname EXEC PGM=IKJEFT01,DYNAMNBR=30   (* TSO in batch *)
//stepname EXEC PROC=procname
//stepname EXEC procname,PARM.step='options'

DD statement patterns

A DD (Data Definition) card binds a logical file name used inside the program to a physical dataset on disk, tape, or SYSOUT. The ddname matches the DDNAME referenced in the program's OPEN call (e.g. SYSIN, SYSPRINT); DISP, DSN, SPACE, and DCB describe how the dataset is managed.

text
//* Sequential dataset
//INFILE   DD DSN=MY.INPUT.FILE,
//             DISP=SHR

//* New dataset
//OUTFILE  DD DSN=MY.OUTPUT.FILE,
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(10,5)),
//             DCB=(RECFM=FB,LRECL=80,BLKSIZE=0),
//             UNIT=SYSDA

//* PDS member
//SYSIN    DD DSN=MY.PROC.LIB(MYMEMBER),DISP=SHR

//* Inline data
//SYSIN    DD *
  data here
/*

//* Sysout
//SYSPRINT DD SYSOUT=*
//SYSOUT   DD SYSOUT=A

//* Dummy (discard)
//SYSIN    DD DUMMY

//* GDG (Generation Data Group)
//INFILE   DD DSN=MY.GDG.FILE(0),DISP=SHR    (* current *)
//INFILE   DD DSN=MY.GDG.FILE(-1),DISP=SHR   (* previous *)
//OUTFILE  DD DSN=MY.GDG.FILE(+1),
//             DISP=(NEW,CATLG,DELETE),...

DISP parameter

DISP is a three-part keyword that tells z/OS the current status of the dataset, what to do with it if the step ends normally, and what to do if the step abends. Omitting the second or third sub-parameter leaves the system to apply a default; the most common combination is (NEW,CATLG,DELETE) for a new dataset you want to keep only on success.

sql
DISP=(status, normal-end, abnormal-end)

Status:       NEW SHR OLD MOD
Normal-end:   KEEP DELETE CATLG UNCATLG PASS
Abnormal-end: KEEP DELETE CATLG UNCATLG

IEFBR14 — do nothing

IEFBR14 is a one-instruction program that returns immediately with RC=0, making it useful as a no-op step when you only want JCL's DD-processing side effects — typically allocating a new dataset or deleting an existing one via DISP=(OLD,DELETE) — without running any real program logic.

text
//ALLOC    EXEC PGM=IEFBR14
//NEWDS    DD DSN=MY.NEW.DATASET,
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(5,2)),
//             DCB=(RECFM=FB,LRECL=80),
//             UNIT=SYSDA

IDCAMS — VSAM utility

IDCAMS (Access Method Services) is the IBM utility for all VSAM dataset lifecycle operations — defining, copying, deleting, and cataloguing KSDS, ESDS, and RRDS clusters. It also handles non-VSAM tasks like REPRO (copy any sequential dataset) and LISTCAT (list the catalog), making it one of the most frequently executed utilities on z/OS.

text
//IDCAMS   EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
  DEFINE CLUSTER -
    (NAME(MY.VSAM.KSDS) -
     RECORDS(1000 100) -
     KEYS(10 0) -
     RECORDSIZE(80 80)) -
    DATA(NAME(MY.VSAM.KSDS.DATA)) -
    INDEX(NAME(MY.VSAM.KSDS.INDEX))
  IF LASTCC = 0 THEN DO
    REPRO INFILE(INDD) OUTFILE(OUTDD)
  END
  DELETE MY.OLD.DATASET PURGE
/*

SET, IF/THEN/ELSE, COND

JCL provides limited procedural control: SET assigns a symbolic variable, COND on an EXEC card skips the step if a previous return code meets a condition, and the IF/THEN/ELSE/ENDIF construct (introduced in JCL for MVS/ESA) allows conditional step execution based on return codes or abend flags. Prefer IF/THEN/ELSE over COND for readability; they can be combined in the same job.

text
//         SET MYVAR=VALUE

//STEP02   EXEC PGM=MYPGM,
//             COND=(0,NE,STEP01)    (* skip if STEP01 RC != 0 *)

// IF (STEP01.RC = 0) THEN
//GOOD     EXEC PGM=GOODPATH
// ELSE
//BAD      EXEC PGM=ERRPATH
// ENDIF

JOB card parameters

The JOB card is a single statement but accepts well over a dozen sub-parameters that govern scheduling, accounting, resource limits, and notification. Most shops standardise on a small subset — CLASS, MSGCLASS, MSGLEVEL, NOTIFY, REGION — and let installation defaults pick up the rest, but every parameter below shows up in production JCL eventually.

ParameterMeaning
CLASS=xJES initiator class (A–Z, 0–9). Controls which initiators are eligible to run the job; sites map classes to resource profiles.
MSGCLASS=xOutput class for job log (JESMSGLG, JESJCL, JESYSMSG). X is typically held SYSOUT; A typically printed.
MSGLEVEL=(s,m)s = statements (0 = JOB only, 1 = JOB+procs expanded, 2 = JOB only no procs); m = messages (0 = JCL errors only, 1 = all). (1,1) is the common dev default.
NOTIFY=&SYSUIDTSO userid to receive an end-of-job message. &SYSUID substitutes the submitter automatically.
REGION=nnnMVirtual storage above and below the 16 MB line. REGION=0M (or 0K) requests "all available" — common for sort and DB2 jobs.
TIME=(m,s)CPU time limit. TIME=1440 and TIME=NOLIMIT disable the limit; TIME=(2,30) is 2 min 30 sec.
TYPRUN=SCAN validates JCL syntax without running; HOLD queues the job in JES HOLD state; JCLHOLD holds for JCL processing only; COPY prints the JCL to SYSOUT and exits.
COND=(rc,op[,step])Suppress remaining steps if a previous return code matches. Job-level form applies to every subsequent step.
RESTART=stepnameBegin execution at stepname (typically used by automated restart tooling after an abend).
PRTY=nJES priority 0–15; higher runs sooner within the same class.
USER= / PASSWORD=Submit the job under a different RACF identity. PASSWORD= is uncommon in modern shops — surrogate authority is preferred.
GROUP=RACF connect group; only useful when the user is connected to more than one group.
ADDRSPC=REALForce the job into non-pageable storage (rare, usually for I/O-sensitive workloads).
BYTES= / LINES= / CARDS= / PAGES=Output volume limits; exceeding any of them causes a S722 abend.
SCHENV=WLM scheduling environment — only initiators that satisfy this resource list will pick up the job.
JESLOG=SUPPRESS discards JES log datasets at job end; SPIN allows mid-job log spinning.
ACCT=Accounting information (free-form) — distinct from the positional accounting field.
text
//ALICEJ01 JOB (BILLG,DEPT42),'Alice Dev',
//             CLASS=A,
//             MSGCLASS=X,
//             MSGLEVEL=(1,1),
//             NOTIFY=&SYSUID,
//             REGION=0M,
//             TIME=(5,0),
//             TYPRUN=SCAN,         (* validate only *)
//             SCHENV=DB2PROD,
//             USER=ALICE

EXEC card parameters

EXEC starts a job step and inherits or overrides many of the same resource limits set on the JOB card. The most-used keywords are PGM=/PROC=, PARM=, COND=, and REGION=; the rest let you fine-tune resource consumption per step.

ParameterMeaning
PGM=nameLoad module to execute. The system searches STEPLIB / JOBLIB / LNKLST.
PROC=name (or name alone)Catalogued or in-stream procedure name; the proc text is expanded into the job at conversion time.
PARM='text'Up to 100 bytes passed to the program in register 1. Quote if it contains commas or special characters.
PARM.step='text'When invoking a PROC, send PARM to a specific step inside the proc.
COND=(rc,op,step)Skip this step if the comparison is true. Multiple tests are AND-ed by default; EVEN/ONLY qualify whether to run on abend.
COND=EVENRun even when an earlier step abended.
COND=ONLYRun ONLY when an earlier step abended (cleanup steps).
REGION=nnnMOverride job-level REGION just for this step.
TIME=(m,s)Override job-level TIME.
ACCT=(info)Accounting info for this step.
ADDRSPC=REALForce this step into non-pageable storage.
DYNAMNBR=nMax concurrent dynamic allocations — increase for TSO-in-batch (IKJEFT01) or REXX EXECs.
PERFORM=nPerformance group (WLM legacy).
text
//STEP01   EXEC PGM=ALICEPGM,
//             PARM='OPTIONS=DEBUG,VERBOSE',
//             REGION=128M,
//             TIME=(2,30),
//             COND=(4,LT,STEP00)        (* skip if STEP00 RC > 4 *)
//STEP02   EXEC PGM=ALICECLEAN,
//             COND=ONLY                 (* cleanup only after an abend *)

PARM= continuation

The PARM string is limited to 100 bytes after concatenation, but the JCL coding rule for splitting a quoted string across cards is non-obvious: close the quote at the end of one card, code // continuation, and reopen the quote at column 16 or later. The system concatenates the segments before passing them to the program.

text
//STEP01   EXEC PGM=ALICEPGM,
//             PARM=('PART1=ALPHA,PART2=BETA,',
//             'PART3=GAMMA,PART4=DELTA')

Symbolic parameters

Symbolic parameters are placeholders in JCL that get substituted at conversion time. They take the form &NAME and are typically defined on a PROC statement (for parameterised procedures) or on a JOB-level SET statement. Symbolics let one piece of JCL serve many jobs without copy-paste editing — change one PARM and every step that references it picks up the new value.

FormWhere definedOverride at
&NAME on PROC statementFirst card of a PROCEXEC card that invokes the proc
System symbolic &SYSUID, &SYSPLEX, &SYSNAME, &SYSCLONESystem parmlibCannot be overridden — always system-supplied
// SET NAME=valueAnywhere before first useRe-SET later in the job
&NAME on EXEC PARMResolved before program sees the valueOverride with PARM='&NAME' substitution
text
//* In-stream PROC with defaults
//PROCNAME PROC HLQ=ALICE,                   (* default HLQ *)
//             RUNDATE=&SYSDATE,             (* system symbolic *)
//             CLASS=A
//STEP01   EXEC PGM=ALICEPGM,REGION=64M
//INFILE   DD DSN=&HLQ..DAILY.&RUNDATE,DISP=SHR
//OUTFILE  DD SYSOUT=&CLASS
//         PEND

//* Invoke with override
//RUN      EXEC PROCNAME,HLQ=DEPT42,CLASS=H

Concatenation gotcha — the double dot

When a symbolic is followed immediately by text that itself starts with a ., JCL requires .. (two dots) so the substitution engine knows where the symbolic name ends. &HLQ.MYDATA is ALICE concatenated with MYDATA; &HLQ..MYDATA is ALICE.MYDATA. This trips up every newcomer at least once.

text
//* &HLQ resolved to ALICE
//WRONG    DD DSN=&HLQ.DAILY,DISP=SHR        (* ALICEDAILY — broken *)
//RIGHT    DD DSN=&HLQ..DAILY,DISP=SHR       (* ALICE.DAILY — correct *)

PROC — procedures

A PROC is a reusable block of JCL — one or more EXEC and DD statements wrapped in // PROC// PEND (in-stream) or stored as a member in a PROCLIB and referenced by name. Catalogued procs live in SYS1.PROCLIB, site procs in shop-specific PDS libraries, and any PDS can be added with JCLLIB.

In-stream PROC

In-stream procs are defined inside the job between // PROC and // PEND, then invoked by the steps that follow. They are convenient for one-off parameterised pipelines.

text
//ALICEJ01 JOB (ACCT),'INSTREAM PROC',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//COPYDS   PROC SRC=,TGT=
//STEP01   EXEC PGM=IEBGENER
//SYSPRINT DD SYSOUT=*
//SYSIN    DD DUMMY
//SYSUT1   DD DSN=&SRC,DISP=SHR
//SYSUT2   DD DSN=&TGT,
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(5,5)),
//             DCB=(RECFM=FB,LRECL=80)
//         PEND
//RUN1     EXEC COPYDS,SRC=ALICE.SOURCE,TGT=ALICE.COPY1
//RUN2     EXEC COPYDS,SRC=ALICE.SOURCE,TGT=ALICE.COPY2

Cataloged PROC + JCLLIB

For procs that live as PDS members, point the job at the proclib with JCLLIB ORDER=() before the first invocation. The search order is left-to-right; SYS1.PROCLIB is always implicitly last.

text
//ALICEJ02 JOB (ACCT),'CATLG PROC',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//         JCLLIB ORDER=(ALICE.PROCLIB,DEPT.PROCLIB)
//         INCLUDE MEMBER=STDSETUP                 (* see INCLUDE below *)
//STEP01   EXEC PROC=DAILYLOAD,HLQ=ALICE,RUNDATE=20260525

Override a proc DD

To override a DD inside a proc, code the DD on the invoking job using stepname.ddname as the DD name. Both the override DD and the proc DD must use compatible attributes.

text
//RUN1     EXEC PROC=DAILYLOAD
//STEP01.INFILE   DD DSN=ALICE.OVERRIDE.INPUT,DISP=SHR
//STEP01.OUTFILE  DD DSN=ALICE.OVERRIDE.OUTPUT,
//                   DISP=(NEW,CATLG,DELETE),
//                   SPACE=(CYL,(10,5))

INCLUDE statement

INCLUDE MEMBER=name pulls another PDS member into the current job at conversion time, before any steps run. The included member can hold DD overrides, SET statements, or whole step sequences; INCLUDE is commonly used for site-wide STEPLIB or RACF setup boilerplate so each job carries only its unique logic.

text
//ALICEJ03 JOB (ACCT),'WITH INCLUDE',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//         JCLLIB ORDER=(ALICE.INCLIB)
//         INCLUDE MEMBER=DB2SETUP        (* STEPLIB to DB2 load, SET DB2SSID=, … *)
//         INCLUDE MEMBER=STDDDS          (* common SYSOUT/SYSPRINT DDs *)
//STEP01   EXEC PGM=ALICEPGM

JOBLIB, STEPLIB, LNKLST

When PGM=name runs, z/OS searches for the load module in this order: STEPLIB (specific to the step), JOBLIB (entire job), LNKLST (system link list defined in SYS1.LINKLIB chain via LNKLSTxx parmlib). STEPLIB is the strongest scope and overrides JOBLIB and the system list.

text
//* JOBLIB applies to every step in this job
//ALICEJ04 JOB (ACCT),'JOBLIB EXAMPLE',CLASS=A,MSGCLASS=X
//JOBLIB   DD DSN=ALICE.LOAD,DISP=SHR
//         DD DSN=DEPT.SHARED.LOAD,DISP=SHR

//STEP01   EXEC PGM=ALICEPGM     (* uses JOBLIB *)

//* STEPLIB overrides JOBLIB for this step only
//STEP02   EXEC PGM=ALICEPGM2
//STEPLIB  DD DSN=TEST.LOAD,DISP=SHR
//         DD DSN=ALICE.LOAD,DISP=SHR

A STEPLIB DD DUMMY or omitted STEPLIB falls through to JOBLIB; an empty JOBLIB falls through to LNKLST. Mixing PDS and PDSE in the same concatenation is allowed but PDSE must come first if its DSNTYPE=LIBRARY modules are to be picked up cleanly.

Continuation and column rules

JCL is a positional language with strict column rules inherited from punched-card days. Get them wrong and the JES2 converter rejects the card with IEFC605I UNIDENTIFIED OPERATION FIELD.

Column(s)Required
1–2// for JCL, //* for comment, /* for delimiter
3–10Name field (jobname, stepname, ddname) — left-justified, 1–8 chars
12+Operation field (JOB, EXEC, DD, SET, PROC, …)
After operationOperands, separated by commas, no embedded blanks
72Continuation indicator — must be blank unless this is a continuation marker (rare)
73–80Sequence area — ignored by the converter, traditionally used for line numbers

Continuation: end the line with a comma after a complete operand, start the next line with // in columns 1–2 and the continuation in column 4–15. Indented continuations make multi-parameter DD statements far more readable.

text
//OUT      DD DSN=ALICE.OUTPUT,
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(10,5),RLSE),
//             DCB=(RECFM=FB,LRECL=80,BLKSIZE=0)

Comment cards use //* and can appear anywhere except inside a continuation. They are stripped at conversion and do not affect operation.

text
//*--------------------------------------------------------------
//* This step builds the daily load file from the master.
//*--------------------------------------------------------------
//STEP01   EXEC PGM=ALICEPGM

Common utility programs

The IBM-supplied utility programs cover most non-application batch work — copy, sort, allocate, catalog, manage VSAM. Use this table as a jump-off; each utility has its own deep-dive article when one exists.

ProgramPurposeDeep-dive
IEFBR14No-op; used to allocate or delete via DD-only side effects(above)
IEBGENERCopy a sequential dataset to another sequential dataset; supports record-level reformat
IEBCOPYCopy or compress a PDS / PDSE; unload to and load from sequentialiebcopy
IDCAMSVSAM lifecycle, catalog, REPRO, LISTCAT, ALTERidcams
SORT / ICETOOLDFSORT — sort, merge, copy, filter, joindfsort
IKJEFT01TSO in batch — run REXX/CLIST or TSO commands from JCLtso-ispf
IKJEFT1BSame as IKJEFT01 but does not abend on non-zero RCtso-ispf
IEBPTPCHPrint and punch (line printer formatted output of dataset)
IEBUPDTEApply update records to a PDS or sequential dataset
IEBCOMPRCompare two sequential datasets or PDS members
IEFUSIInstallation SMF exit (not invoked directly)
IEHLISTList VTOC contents and catalog entries
IEHMOVELegacy move/copy — superseded by DFSMSdss
ADRDSSUDFSMSdss — disk dump/restore, copy, defrag

A minimal IEBGENER copy with record-level reformat (the GENERATE/RECORD control statements):

text
//COPY     EXEC PGM=IEBGENER
//SYSPRINT DD SYSOUT=*
//SYSUT1   DD DSN=ALICE.SOURCE.FB80,DISP=SHR
//SYSUT2   DD DSN=ALICE.TARGET.FB80,
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(5,5),RLSE),
//             DCB=(RECFM=FB,LRECL=80)
//SYSIN    DD *
   GENERATE MAXFLDS=1
   RECORD FIELD=(10,1,,1)            (* copy bytes 1-10 to position 1 *)
/*

See jcl-dd for the full DD-statement reference, including DCB, SPACE, DISP truth tables, GDG syntax, concatenation, DUMMY, and SMS classes.

Real-world recipes

Multi-step ETL pipeline with PASSed temp dataset

A daily extract job runs in three stages: pull from VSAM, sort, then load. The intermediate file is a job-temporary dataset (&&) passed between steps and deleted automatically at job end.

text
//ALICEJ05 JOB (ACCT),'DAILY ETL',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//*
//* STEP01 - extract from VSAM
//*
//STEP01   EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//IN       DD DSN=ALICE.VSAM.MASTER,DISP=SHR
//OUT      DD DSN=&&EXTRACT,
//             DISP=(NEW,PASS),
//             SPACE=(CYL,(50,10),RLSE),
//             DCB=(RECFM=FB,LRECL=200)
//SYSIN    DD *
   REPRO INFILE(IN) OUTFILE(OUT)
/*
//*
//* STEP02 - sort by customer key
//*
//STEP02   EXEC PGM=SORT,COND=(0,NE,STEP01)
//SORTIN   DD DSN=&&EXTRACT,DISP=(OLD,PASS)
//SORTOUT  DD DSN=&&SORTED,
//             DISP=(NEW,PASS),
//             SPACE=(CYL,(50,10),RLSE),
//             DCB=(RECFM=FB,LRECL=200)
//SYSOUT   DD SYSOUT=*
//SYSIN    DD *
   SORT FIELDS=(1,12,CH,A)
/*
//*
//* STEP03 - load to daily target dataset (GDG)
//*
//STEP03   EXEC PGM=IEBGENER,COND=(0,NE,STEP02)
//SYSPRINT DD SYSOUT=*
//SYSIN    DD DUMMY
//SYSUT1   DD DSN=&&SORTED,DISP=(OLD,DELETE)
//SYSUT2   DD DSN=ALICE.DAILY.LOAD(+1),
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(100,20),RLSE),
//             DCB=(RECFM=FB,LRECL=200)

Conditional restart-on-failure

A two-leg job runs the main batch, and if it abends, dispatches a notification step and an orderly cleanup step using COND=ONLY.

text
//ALICEJ06 JOB (ACCT),'RESTART LOGIC',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP01   EXEC PGM=ALICEMAIN,REGION=128M
//INFILE   DD DSN=ALICE.INPUT,DISP=SHR
//OUTFILE  DD DSN=ALICE.OUTPUT,
//             DISP=(NEW,CATLG,KEEP),
//             SPACE=(CYL,(50,10),RLSE),
//             DCB=(RECFM=FB,LRECL=200)
//*
//* Notification step runs only on abend
//*
//STEP02   EXEC PGM=IEBGENER,COND=ONLY
//SYSPRINT DD SYSOUT=*
//SYSIN    DD DUMMY
//SYSUT1   DD *
ALICEJ06 ABENDED IN STEP01 - SEE SDSF
/*
//SYSUT2   DD SYSOUT=(*,INTRDR)
//*
//* Cleanup step also runs only on abend
//*
//STEP03   EXEC PGM=IEFBR14,COND=ONLY
//KILL     DD DSN=ALICE.OUTPUT,DISP=(OLD,DELETE)

Parameterised PROC with overrides

A cataloged proc that loads any dataset into a daily GDG; the calling jobs pass the source dataset and skip date via symbolics, and one job overrides the OUTFILE allocation entirely.

text
//* Stored as ALICE.PROCLIB(DAILYLD)
//DAILYLD  PROC SRC=,SKIP=0
//STEP01   EXEC PGM=ALICEPGM,PARM='SKIP=&SKIP'
//STEPLIB  DD DSN=ALICE.LOAD,DISP=SHR
//INFILE   DD DSN=&SRC,DISP=SHR
//OUTFILE  DD DSN=ALICE.DAILY.LOAD(+1),
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(100,20),RLSE),
//             DCB=(RECFM=FB,LRECL=200)
//SYSOUT   DD SYSOUT=*
//         PEND

//* Caller 1 - defaults
//ALICEJ07 JOB (ACCT),'DAILY 1',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//         JCLLIB ORDER=(ALICE.PROCLIB)
//RUN      EXEC PROC=DAILYLD,SRC=ALICE.SRC.A

//* Caller 2 - override OUTFILE allocation
//ALICEJ08 JOB (ACCT),'DAILY 2',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//         JCLLIB ORDER=(ALICE.PROCLIB)
//RUN      EXEC PROC=DAILYLD,SRC=ALICE.SRC.B,SKIP=1000
//STEP01.OUTFILE DD DSN=ALICE.WEEKLY.SUMMARY,
//                  DISP=(NEW,CATLG,DELETE),
//                  SPACE=(CYL,(500,100),RLSE),
//                  DCB=(RECFM=FB,LRECL=200)

GDG-aware backup with retention

A weekly backup keeps the last four generations; the GDG base is defined once with IDCAMS, then ordinary JCL allocates each new (+1) generation. RETPD=28 deletes generations after four weeks regardless of GDG limit.

text
//* One-time setup (run separately)
//SETUP    EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN    DD *
   DEFINE GENERATIONDATAGROUP                                      -
     (NAME(ALICE.WEEKLY.BACKUP)                                    -
      LIMIT(4)                                                     -
      SCRATCH                                                      -
      NOEMPTY)
/*

//* Weekly backup job
//ALICEJ09 JOB (ACCT),'WEEKLY BACKUP',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP01   EXEC PGM=IEBGENER
//SYSPRINT DD SYSOUT=*
//SYSIN    DD DUMMY
//SYSUT1   DD DSN=ALICE.PROD.MASTER,DISP=SHR
//SYSUT2   DD DSN=ALICE.WEEKLY.BACKUP(+1),
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(500,100),RLSE),
//             DCB=(RECFM=FB,LRECL=200),
//             RETPD=28

A classic three-step language pipeline: compile a COBOL source into an object, link the object into a load module, then execute it. The first two steps use cataloged IBM procs; the third is a hand-coded EXEC.

text
//ALICEJ11 JOB (ACCT),'CLG',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//*
//* Compile
//*
//COMP     EXEC PROC=IGYWC,PARM.COBOL='LIST,MAP,XREF'
//COBOL.SYSIN    DD DSN=ALICE.SRC.COBOL(ALICEPGM),DISP=SHR
//COBOL.SYSLIN   DD DSN=&&OBJ,DISP=(NEW,PASS),
//                  SPACE=(CYL,(1,1)),
//                  DCB=(RECFM=FB,LRECL=80)
//*
//* Link-edit
//*
//LKED     EXEC PROC=IEWL,COND=(0,LT,COMP)
//SYSLIN   DD DSN=&&OBJ,DISP=(OLD,DELETE)
//         DD DDNAME=SYSIN
//SYSLMOD  DD DSN=ALICE.LOAD(ALICEPGM),DISP=SHR
//SYSIN    DD *
   NAME ALICEPGM(R)
/*
//*
//* Run
//*
//GO       EXEC PGM=ALICEPGM,COND=(0,LT,LKED)
//STEPLIB  DD DSN=ALICE.LOAD,DISP=SHR
//INFILE   DD DSN=ALICE.SAMPLE.INPUT,DISP=SHR
//OUTFILE  DD SYSOUT=*

TYPRUN=SCAN validation step

To validate a JCL change without actually running it, submit with TYPRUN=SCAN — the JES converter parses every card and emits JCL errors but no step executes. Useful in a CI pipeline that lints JCL on every commit.

text
//ALICEJ10 JOB (ACCT),'SCAN ONLY',CLASS=A,MSGCLASS=X,
//             TYPRUN=SCAN,
//             NOTIFY=&SYSUID
//STEP01   EXEC PGM=IEFBR14
//DD1      DD DSN=ALICE.WOULD.BE.NEW,
//             DISP=(NEW,CATLG,DELETE),
//             SPACE=(CYL,(10,5),RLSE),
//             DCB=(RECFM=FB,LRECL=80)

Sources

References consulted while writing this article. Links open in a new tab.

  • IBM Documentation — z/OS MVS JCL Reference — Authoritative reference for the JOB / EXEC / DD syntax and disposition/space/dcb parameters covered in this article.

Common pitfalls

  1. Lowercase outside quotes — JCL is case-sensitive at the token level: Disp=Shr will be flagged. Always uppercase keywords, dataset names, and values; only quoted strings preserve case.
  2. Column-72 continuation — a stray character in column 72 turns into a continuation indicator and the next card is treated as a continuation. Many editors hide column 72; turn the column ruler on (COLS in ISPF Edit).
  3. Missing comma at end of continued line — a continuation line is recognised by the absence of a complete statement, signalled by ending the previous line with a comma after a complete operand. Forgetting the comma yields IEFC662I "INVALID LABEL".
  4. PROC override stepname.ddname mismatch — the stepname must match the step name inside the proc, not an arbitrary alias. Spell it the same way the proc does.
  5. JCL error vs allocation errorIEFC*** messages come from the converter before any step runs (JCL syntax problem); IEC*** and IGD*** come from allocation at step start (real-world: dataset doesn't exist, RACF denied, volume full). Look at the message prefix to know which side of the fence the failure happened on.
  6. DISP=NEW on an existing datasetIGD17001I for SMS, IEC141I 013 for non-SMS. Either delete first with IEFBR14 + DISP=(OLD,DELETE), or use DISP=MOD to append, or DISP=OLD to reuse.
  7. COND= polarity confusionCOND=(rc,op,step) skips the step when the comparison is true. COND=(0,NE,STEP01) reads "skip if STEP01's RC is not equal to 0", i.e. only run on success. Use IF/THEN/ELSE for anything more complex than one test.
  8. JCLLIB before INCLUDEJCLLIB must precede the first INCLUDE or EXEC PROC= reference. Otherwise the converter can't find the member.
  9. Symbolic substitution and the double dot&HLQ.DATA concatenates without a dot; &HLQ..DATA produces HLQ.DATA. The same rule applies to every text-adjacent symbolic reference.
  10. In-stream /* inside data — the /* delimiter for DD * ends the in-stream block. If your data legitimately contains /* in column 1, use DD *,DLM=@@ and end the block with @@, or use DD DATA which only terminates at //.
  11. REGION=0M without site approval — some shops reject jobs above a certain region size at the JES2 init exit. Check your installation default before assuming "0M means unlimited."
  12. Forgetting JOBLIB is job-wide — a JOBLIB DD defined in step 1 still applies to step 7. Use STEPLIB for step-scoped library overrides; don't put environment-specific libraries in JOBLIB.
  13. NOTIFY=&SYSUID failing on submitted-from-batch jobs&SYSUID resolves to the job's own userid, which may be a functional ID with no TSO session. Hardcode a real TSO id (NOTIFY=ALICE) for batch-of-batch.
  14. GDG (+1) referenced in a later step without DISP=PASS — the new generation is not catalogued until job end; step 2 sees the catalog as it was at job start. Code DISP=(NEW,PASS) then DISP=(OLD,CATLG).
  15. Tape jobs with EXPDT=99000 — at some sites this is interpreted as "never scratch"; at others as a literal 1999 date. Use the unambiguous EXPDT=1999/365 form.