LearnAI ToolsCareerPractice BuildsPlayContact
Mainframe SystemsIntermediate~2 hours

Model a Nightly Batch Cycle

Design the JCL and PROC structure for a multi-step nightly batch update job.

JCL ProceduresBatch ProcessingScheduling

Overview

A single-step job like Project 1's is rare in production — a real nightly cycle is a chain: extract today's transactions, validate them, apply them to the master file, back up the result, and only then let downstream reporting jobs start. Writing that as five separate one-step jobs would work, but it would also mean copy-pasting the same DD statements into every job that touches the same datasets, and a change to a dataset name would need updating in five places instead of one. A cataloged PROC solves this: it is a reusable block of JCL steps, stored once in a procedure library, invoked with `EXEC procname` from any job the same way a function is called from multiple places in a program.

This project designs a four-step nightly cycle — extract, validate, update, backup — packaged as a cataloged PROC with symbolic parameters, and wires the steps together with both the classic `COND=` parameter and its modern replacement, `IF/THEN/ENDIF`, so each step only runs when the ones before it actually succeeded. By the end you will understand why later steps in a batch chain need to be conditional at all, and how this single PROC would sit inside a scheduler's nightly batch window alongside the other jobs it depends on.

What You'll Build
  • A four-step nightly cycle: extract, validate, update, backup.
  • A cataloged `PROC` with symbolic parameters for the input dataset and run date.
  • Step dependencies expressed with the classic `COND=` parameter.
  • The same dependencies rewritten with structured `IF/THEN/ENDIF` logic.
  • A calling job that invokes the PROC, and a batch-window schedule showing where it fits among other nightly jobs.

Prerequisites

  • A complete single-step JCL job (Project 1) — JOB/EXEC/DD anatomy and DISP parameters.
  • A planned dataset layout (Project 2) — this project runs directly against STU.DAILY.TRANS and STU.MASTER.KSDS.
  • What a return code is, and that condition codes are set per step, not per job.
  • The general idea of a symbolic parameter — a placeholder substituted with a real value at invocation time, similar to a function argument.

Project Structure

The PROC lives in a procedure library and never runs on its own — a separate, short calling job supplies the symbolic parameter values and invokes it. That split matters operationally: the PROC's logic (what steps run, in what order, with what dependencies) changes rarely and lives in one place, while the calling job's parameter values (which day's transaction file, which run date) change every single night without the PROC itself needing to be touched.

ComponentLives InChanges
NIGHTBAT PROCSTU.PROD.JCLLIB(NIGHTBAT)Rarely — only when the batch logic itself changes
EXTRACT stepInside the PROCPulls the day's transactions from &TRANSDS
VALIDATE stepInside the PROCRuns only if EXTRACT succeeded
UPDATE stepInside the PROCRuns only if VALIDATE found zero rejects
BACKUP stepInside the PROCRuns only if UPDATE succeeded
Calling jobSTU.PROD.JCLLIB(RUNNIGHT)Every night — supplies &RUNDATE and &TRANSDS

Step 1: Design the Batch Cycle Steps

Before writing any JCL, the four steps and their dependencies need to be laid out as a plan: what each step does, what it reads and writes, and — critically — under what condition the next step should even attempt to run. `VALIDATE` is the interesting one: it is designed to set a return code based on how many transactions it rejected, not just whether it crashed, so `UPDATE` can refuse to touch the master file at all if validation found anything wrong, rather than applying a half-good batch of transactions.

StepProgramPurposeRuns If
EXTRACTSTUEXTRCopy today's transactions from the live feed into STU.DAILY.TRANSAlways (first step)
VALIDATESTUVALIDCheck every transaction for a valid student ID and legal action codeEXTRACT ended RC 0
UPDATESTUUPDTApply validated transactions to STU.MASTER.KSDSVALIDATE ended RC 0 (zero rejects)
BACKUPIDCAMS (REPRO)Copy the updated master to a dated backup datasetUPDATE ended RC 0

Step 2: Write the Cataloged PROC

A PROC looks like an ordinary sequence of EXEC/DD statements, but it starts with a `PROC` statement (rather than `JOB`) and ends with `PEND` (PROC end), and its DD statements reference symbolic parameters — names prefixed with `&` — instead of hardcoded dataset names. `&TRANSDS` and `&RUNDATE` are declared with default values on the `PROC` statement itself; a calling job can accept those defaults or override them, which is exactly what makes one PROC reusable across many nights instead of needing to be edited every time.

//NIGHTBAT PROC TRANSDS='STU.DAILY.TRANS',RUNDATE=TODAY
//*
//* PROC (not JOB) marks this as a reusable procedure, not a submittable
//* job on its own. TRANSDS and RUNDATE are symbolic parameters with
//* default values - a calling job can override either one.
//*
//EXTRACT EXEC PGM=STUEXTR
//SYSPRINT DD SYSOUT=*
//TRANSOUT DD DSN=&TRANSDS,DISP=(NEW,CATLG,DELETE),
// SPACE=(TRK,(20,10),RLSE),
// DCB=(RECFM=FB,LRECL=80,BLKSIZE=27920)
//*
//VALIDATE EXEC PGM=STUVALID,COND=(0,NE,EXTRACT)
//SYSPRINT DD SYSOUT=*
//TRANSIN DD DSN=&TRANSDS,DISP=SHR
//REJECTS DD SYSOUT=*
//*
//UPDATE EXEC PGM=STUUPDT,COND=((0,NE,EXTRACT),(0,NE,VALIDATE))
//SYSPRINT DD SYSOUT=*
//TRANSIN DD DSN=&TRANSDS,DISP=SHR
//MASTER DD DSN=STU.MASTER.KSDS,DISP=SHR
//*
//BACKUP EXEC PGM=IDCAMS,
// COND=((0,NE,EXTRACT),(0,NE,VALIDATE),(0,NE,UPDATE))
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
REPRO INFILE(MASTIN) OUTFILE(MASTOUT)
/*
//MASTIN DD DSN=STU.MASTER.KSDS,DISP=SHR
//MASTOUT DD DSN=STU.MASTER.BACKUP.G(+1),
// DISP=(NEW,CATLG,DELETE),
// LIKE=STU.MASTER.KSDS
// PEND
//*
//* PEND marks the end of the procedure's steps.

Step 3: Add Dependencies with COND

`COND=(0,NE,EXTRACT)` on the VALIDATE step reads, somewhat backwards from how it looks: "bypass this step if EXTRACT's return code is NOT EQUAL to 0" — COND parameters describe the condition under which a step is *skipped*, not the condition under which it runs, which is the single most common source of COND mistakes for anyone new to JCL. Each later step accumulates the checks of everything before it, because COND on one step does **not** automatically check earlier steps beyond its own explicit reference — UPDATE's `COND=((0,NE,EXTRACT),(0,NE,VALIDATE))` has to name both, or a VALIDATE failure could be silently skipped over.

COND's backwards logic is a known trap

`COND=(4,LT,STEP1)` means "skip this step if 4 is less than STEP1's return code" — i.e. skip if STEP1's RC is greater than 4. Every comparison is written from the return code's point of view, not the step's, which is exactly why IBM introduced IF/THEN/ENDIF as a clearer replacement in later z/OS releases.

Step 4: Modernize with IF/THEN/ENDIF

IF/THEN/ENDIF reads in the direction a step dependency is actually meant to be read: "if the previous step's return code was acceptable, THEN run the next one." `&LASTCC` refers to the immediately preceding step's condition code, and `&MAXCC` refers to the highest condition code any step in the job has reached so far — using `&MAXCC` in later checks means a single early failure stays remembered even if an intervening step happens to end cleanly. The same four-step dependency chain from Step 3, rewritten this way, is both shorter and does not require mentally inverting any comparisons.

//EXTRACT EXEC PGM=STUEXTR
//SYSPRINT DD SYSOUT=*
//TRANSOUT DD DSN=&TRANSDS,DISP=(NEW,CATLG,DELETE),
// SPACE=(TRK,(20,10),RLSE),
// DCB=(RECFM=FB,LRECL=80,BLKSIZE=27920)
//*
//IF1 IF (EXTRACT.RC = 0) THEN
//*
//* Reads exactly as written: IF EXTRACT's return code equals 0, THEN
//* the steps below run. No inverted "skip if NOT equal" logic to parse.
//*
//VALIDATE EXEC PGM=STUVALID
//SYSPRINT DD SYSOUT=*
//TRANSIN DD DSN=&TRANSDS,DISP=SHR
//REJECTS DD SYSOUT=*
//*
//IF2 IF (VALIDATE.RC = 0) THEN
//UPDATE EXEC PGM=STUUPDT
//SYSPRINT DD SYSOUT=*
//TRANSIN DD DSN=&TRANSDS,DISP=SHR
//MASTER DD DSN=STU.MASTER.KSDS,DISP=SHR
//*
//IF3 IF (UPDATE.RC = 0) THEN
//BACKUP EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
REPRO INFILE(MASTIN) OUTFILE(MASTOUT)
/*
//MASTIN DD DSN=STU.MASTER.KSDS,DISP=SHR
//MASTOUT DD DSN=STU.MASTER.BACKUP.G(+1),
// DISP=(NEW,CATLG,DELETE),
// LIKE=STU.MASTER.KSDS
//ENDIF3 ENDIF
//ENDIF2 ENDIF
//ENDIF1 ENDIF
// PEND

Step 5: Invoke the PROC and Fit the Schedule

The calling job is deliberately tiny: it names the PROC on an `EXEC` statement and overrides the symbolic parameters that change nightly. Everything else — the four steps, their DDs, their dependency logic — is defined once inside `NIGHTBAT` and never repeated here. In production this whole job would not be submitted by hand each night; a job scheduler (CA-7, Control-M, or z/OS's own automatic-restart facilities are common choices) triggers it inside a defined batch window, typically after online CICS/TSO usage drops off and before the next business day's online systems need the updated master file back.

//RUNNIGHT JOB (ACCT123),'J SMITH',CLASS=A,MSGCLASS=X,
// NOTIFY=&SYSUID
//STEP1 EXEC NIGHTBAT,RUNDATE=20260810
//*
//* EXEC NIGHTBAT invokes the cataloged PROC by name - all four steps
//* inside it (EXTRACT/VALIDATE/UPDATE/BACKUP) run as part of this job.
//* RUNDATE overrides the PROC's default; TRANSDS is left at its default.
Time WindowJobDepends On
22:00 - 22:15Online CICS region shutdown—
22:15 - 22:45RUNNIGHT (this PROC: extract/validate/update/backup)CICS shutdown complete
22:45 - 23:15Downstream reporting jobs (enrollment summary, etc.)RUNNIGHT ended RC 0
23:15 - 05:00Full-volume tape backupsAll batch jobs complete
05:00Online CICS region startupBackups complete

Complete JCL

The PROC (IF/THEN/ENDIF version, the current recommended style) and its calling job together, in the order they would be stored: the PROC in the procedure library, the calling job submitted each night.

//* --- Member: STU.PROD.JCLLIB(NIGHTBAT) ---
//NIGHTBAT PROC TRANSDS='STU.DAILY.TRANS',RUNDATE=TODAY
//EXTRACT EXEC PGM=STUEXTR
//SYSPRINT DD SYSOUT=*
//TRANSOUT DD DSN=&TRANSDS,DISP=(NEW,CATLG,DELETE),
// SPACE=(TRK,(20,10),RLSE),
// DCB=(RECFM=FB,LRECL=80,BLKSIZE=27920)
//IF1 IF (EXTRACT.RC = 0) THEN
//VALIDATE EXEC PGM=STUVALID
//SYSPRINT DD SYSOUT=*
//TRANSIN DD DSN=&TRANSDS,DISP=SHR
//REJECTS DD SYSOUT=*
//IF2 IF (VALIDATE.RC = 0) THEN
//UPDATE EXEC PGM=STUUPDT
//SYSPRINT DD SYSOUT=*
//TRANSIN DD DSN=&TRANSDS,DISP=SHR
//MASTER DD DSN=STU.MASTER.KSDS,DISP=SHR
//IF3 IF (UPDATE.RC = 0) THEN
//BACKUP EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
REPRO INFILE(MASTIN) OUTFILE(MASTOUT)
/*
//MASTIN DD DSN=STU.MASTER.KSDS,DISP=SHR
//MASTOUT DD DSN=STU.MASTER.BACKUP.G(+1),
// DISP=(NEW,CATLG,DELETE),
// LIKE=STU.MASTER.KSDS
//ENDIF3 ENDIF
//ENDIF2 ENDIF
//ENDIF1 ENDIF
// PEND
//* --- Member: STU.PROD.JCLLIB(RUNNIGHT) ---
//RUNNIGHT JOB (ACCT123),'J SMITH',CLASS=A,MSGCLASS=X,
// NOTIFY=&SYSUID
//STEP1 EXEC NIGHTBAT,RUNDATE=20260810

Sample Run

Submitting `RUNNIGHT` on a night where 312 transactions extract cleanly and validate with zero rejects produces a full run through all four steps.

SDSF Job Log Excerpt

Click Run to see what this code prints.

Extend This Project

  • Add an ELSE branch to `IF2` that routes rejected transactions to an exception report instead of simply skipping UPDATE silently.
  • Add a fifth step, `NOTIFY`, that sends a completion email/message only when BACKUP finishes, using `&MAXCC` to detect any earlier failure even if BACKUP itself is skipped.
  • Parametrize `STU.MASTER.BACKUP.G(+1)` as a true generation data group (GDG) and explain what `(+1)`, `(0)`, and `(-1)` each refer to.
  • Add a `RESTART=UPDATE` example showing how an operator would restart the job at the UPDATE step after fixing a problem, without rerunning EXTRACT and VALIDATE.
  • Convert `COND=` version from Step 3 fully to IF/THEN/ENDIF and compare the two side by side for a five-step chain.

Summary

You packaged a four-step nightly batch cycle into a reusable, cataloged PROC with symbolic parameters, and wired its steps together two different ways — the classic `COND=` parameter and the clearer `IF/THEN/ENDIF` structure — so a validation failure or an update failure stops the chain instead of silently corrupting the next step's input. The calling-job/PROC split and the batch-window table are the same shape every real nightly cycle takes: logic defined once in a procedure library, invoked nightly by a scheduler, with just enough conditional logic to fail safely instead of failing silently.