cheat sheet
GDG
Define, reference, and clean up z/OS generation data groups via IDCAMS — relative generations, rollover behaviour, model DSCBs, and the modern GDGEXTENDED mode.
GDG — Generation data groups for rolling datasets
What it is
A Generation Data Group (GDG) is a z/OS catalog construct that lets you refer to a series of dated dataset versions by a single base name and a relative index — (0) for the current generation, (-1) for the previous, (+1) for the next one being created. Behind the scenes each generation is a fully independent dataset stored as BASE.GnnnnV00 (where nnnn is the generation number); the GDG base itself is metadata only, holding the rolling list of physical generations and the policy that controls when old ones get rolled off. Reach for GDGs anytime you run the same job repeatedly against the same logical input — daily extracts, hourly backups, weekly statements, rolling audit logs — and want to keep N historical copies without renaming dataset names in JCL every run; for the modern, IDCAMS-managed variant that supports up to 999 generations and EXTENDED features, use GDGEXTENDED(YES) on the define, and for VSAM clusters use a clusters-with-EXTENDED scheme instead of a GDG.
How it works
The GDG base is created once with IDCAMS DEFINE GENERATIONDATAGROUP. The base never holds any data — it is a catalog node listing the currently active generations and the policy (LIMIT, SCRATCH/NOSCRATCH, EMPTY/NOEMPTY) for managing them.
Each new generation is allocated by JCL using a relative number (+1) and a DCB that comes either from explicit attributes on the DD card or, more commonly, from a "model DSCB" — a tiny zero-track dataset whose DCB attributes IDCAMS records and copies to each new generation. After the JCL step completes successfully, JES2 "rolls over" the new generation: it gets the next sequential GnnnnV00 number, the relative numbers of the older generations shift down (the old (0) becomes the new (-1)), and if the count exceeds LIMIT the oldest generation is scratched or uncatalogued according to the policy.
BASE.GDG (* the GDG base — catalog entry only *)
├── BASE.GDG.G0001V00 (* generation 1 — relative (-3) today *)
├── BASE.GDG.G0002V00 (* generation 2 — relative (-2) today *)
├── BASE.GDG.G0003V00 (* generation 3 — relative (-1) today *)
├── BASE.GDG.G0004V00 (* generation 4 — relative (0) today *)
└── BASE.GDG.G0005V00 (* generation 5 — relative (+1), in-flight *)
Define the GDG base
IDCAMS DEFINE GENERATIONDATAGROUP creates the base entry. The most important parameter is LIMIT(n) — the maximum number of generations to retain before the oldest is rolled off. SCRATCH (the default) physically deletes the rolled-off generation; NOSCRATCH only uncatalogues it, leaving the data on volume for manual cleanup.
//DEFGDG JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DEFINE GENERATIONDATAGROUP -
(NAME(ALICE.BACKUP.GDG) -
LIMIT(14) -
SCRATCH -
NOEMPTY)
/*
Define parameters
| Parameter | Meaning |
|---|---|
NAME(base) | Required. The base name; generations become base.GnnnnV00. |
LIMIT(n) | Required. Maximum live generations before rollover. 1–255 for classic, 1–999 for GDGEXTENDED. |
SCRATCH | Rolled-off generations are physically deleted (default). |
NOSCRATCH | Rolled-off generations are uncatalogued only — data remains on volume. |
EMPTY | When LIMIT is reached, ALL generations are rolled off, not just the oldest. |
NOEMPTY | When LIMIT is reached, only the oldest is rolled off (default — start here). |
OWNER(userid) | RACF owner of the GDG base. |
TO(date) / FOR(days) | Expiration of the base entry itself. |
GDGEXTENDED(YES) | Use the extended GDG features (LIMIT up to 999, additional management options). |
Choosing EMPTY vs NOEMPTY
NOEMPTY is the right default for almost every workload: a daily extract that runs continuously, rolling off generation G0001V00 after the 15th run, keeping G0002V00 through G0015V00 live.
EMPTY only makes sense for jobs that batch-replace the whole window each run — for example, monthly full snapshots where each new run completely supersedes the previous month's set. When LIMIT is hit, the next allocation purges everything and starts from G0001V00 again.
Allocate the model DSCB
A model DSCB is a zero-track dummy dataset whose DCB attributes (RECFM, LRECL, BLKSIZE) IDCAMS copies to each new generation when JCL specifies the GDG by relative number without explicit DCB. One model can serve any number of GDGs.
//MODEL JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X
//STEP1 EXEC PGM=IEFBR14
//MODELDS DD DSN=ALICE.GDG.MODEL,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(TRK,0),
// DCB=(RECFM=FB,LRECL=80,BLKSIZE=27920),
// UNIT=SYSDA,
// VOL=SER=USR001
Reference the model in subsequent GDG-using JCL via DCB=(MODEL.NAME,...), which tells JCL "take all DCB attributes from this dataset's DSCB unless explicitly overridden":
//OUTPUT DD DSN=ALICE.BACKUP.GDG(+1),
// DISP=(NEW,CATLG,DELETE),
// DCB=ALICE.GDG.MODEL,
// SPACE=(CYL,(10,5))
On modern z/OS with SMS-managed datasets, the model DSCB is often optional — SMS DATACLAS supplies the same defaults — but explicit models still work and are the most portable way to template GDG generations.
Relative generation numbers in JCL
(0), (+1), (-1), (-2) ... reference generations relative to the GDG's current state at the time the job is read by JES2. The numbering is locked at job-read time, not at job-execution time — this matters when long-running batch chains overlap with parallel jobs that also roll the GDG.
//* Read the most recent generation
//INPUT DD DSN=ALICE.BACKUP.GDG(0),DISP=SHR
//* Read the generation before the most recent
//PREVIN DD DSN=ALICE.BACKUP.GDG(-1),DISP=SHR
//* Read three generations back
//OLDIN DD DSN=ALICE.BACKUP.GDG(-3),DISP=SHR
//* Create the next generation
//OUTPUT DD DSN=ALICE.BACKUP.GDG(+1),
// DISP=(NEW,CATLG,DELETE),
// DCB=ALICE.GDG.MODEL,
// SPACE=(CYL,(10,5)),
// UNIT=SYSDA
Within a single job, multiple DDs referencing the same (+1) always resolve to the same physical generation — JES2 records the allocation once and reuses it. A second (+1) in a different job would allocate a second new generation.
Multi-step jobs that need to read what they wrote in an earlier step use (+1) in both DDs:
//STEP1 EXEC PGM=GENERATE
//OUTPUT DD DSN=ALICE.DAILY.GDG(+1),
// DISP=(NEW,CATLG,DELETE),
// DCB=ALICE.GDG.MODEL,
// SPACE=(CYL,(10,5)),
// UNIT=SYSDA
//*
//STEP2 EXEC PGM=VERIFY
//INPUT DD DSN=ALICE.DAILY.GDG(+1),DISP=SHR
JCL knows STEP2's (+1) is the same dataset STEP1 just created and resolves it without rolling the GDG twice.
Absolute generation names
Every generation has an absolute name of the form BASE.GnnnnV00 where nnnn is the four-digit generation number (1–9999, wrapping back to 0001 after 9999) and V00 is the version — almost always 00 in practice; JCL keyword VERSION= allows up to 99 historical versions of one generation, a feature rarely used.
Reference an absolute name when:
- The generation is no longer in the GDG (rolled off but
NOSCRATCHpreserved it). - You want to reference a specific historical generation that has shifted relative position.
- IDCAMS commands operate on the generation as a normal dataset.
//INFILE DD DSN=ALICE.BACKUP.GDG.G0042V00,DISP=SHR
IDCAMS LISTCAT shows the absolute names and which are currently in the GDG list:
//STEP1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
LISTCAT ENTRIES(ALICE.BACKUP.GDG) ALL
/*
Rollover behaviour
After a job that creates (+1) completes successfully, JES2 issues the JES rollover: the new GnnnnV00 is added to the GDG's catalog list, the relative numbers shift, and if the count now exceeds LIMIT:
SCRATCH— the oldest generation's data and catalog entry are both deleted.NOSCRATCH— only the catalog entry is removed; the physical dataset remains on volume, uncatalogued, eligible forIDCAMS DELETEcleanup or HSM migration.
If the job ABENDs before STEP3 completes, the (+1) created in STEP1 is not rolled over — it remains in "in-flight" status, catalogued under the absolute name but not yet in the GDG list. Re-running the job re-creates (+1) as the same absolute name or the next one, depending on the DISP=(NEW,CATLG,DELETE) recovery path.
The DISP=(NEW,CATLG,DELETE) on the (+1) DD is conventional: if the step ABENDs, the in-flight generation is uncatalogued and deleted, leaving the GDG in its pre-job state. DISP=(NEW,CATLG,KEEP) would preserve the in-flight generation for forensics, but pollutes the catalog.
GDGEXTENDED mode
Classic GDGs support LIMIT up to 255 and store the generation list in a single catalog cell, which becomes a contention point for very high-volume GDGs. GDGEXTENDED(YES) (also called "extended GDGs") raises the limit to 999 and uses a different catalog structure that supports parallel allocation without serializing through one catalog entry.
DEFINE GENERATIONDATAGROUP -
(NAME(ALICE.HIGH.VOLUME.GDG) -
LIMIT(500) -
SCRATCH -
NOEMPTY -
GDGEXTENDED(YES))
You can also enable EXTENDED mode globally via the IGGCATxx SYS1.PARMLIB member or for a specific GDG via the EXTENDED keyword on ALTER:
ALTER ALICE.BACKUP.GDG GDGEXTENDED(YES) LIMIT(500)
Existing classic GDGs can be migrated to EXTENDED mode without re-defining; the catalog rewrites itself on the next reference. There is no rollback to classic mode after migration short of redefining the base.
ALTER — change LIMIT or policy
IDCAMS ALTER changes the GDG base attributes without redefining (which would lose the catalog history). Useful when retention requirements shift — e.g. compliance now requires 30 days of backups instead of 14.
//STEP1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
ALTER ALICE.BACKUP.GDG LIMIT(30)
ALTER ALICE.BACKUP.GDG NOSCRATCH
ALTER ALICE.BACKUP.GDG EMPTY
/*
After raising LIMIT, existing generations are unaffected; new generations roll off later. After lowering LIMIT, the next allocation immediately rolls off enough generations to fit the new limit — so lowering from 30 to 14 with 25 current generations would scratch 11 the next time anyone creates a (+1).
Delete generations and the base
To delete one generation, treat it as a normal dataset:
//STEP1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DELETE ALICE.BACKUP.GDG.G0042V00
/*
To delete the entire GDG including every generation and the base entry, use DELETE … GENERATIONDATAGROUP FORCE:
//STEP1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DELETE ALICE.BACKUP.GDG GENERATIONDATAGROUP FORCE
/*
FORCE is required when the GDG still has live generations — without it, IDCAMS rejects the delete with IDC3009I VSAM CATALOG RETURN CODE IS 56.
To delete every generation but keep the base for reuse:
DELETE ALICE.BACKUP.GDG.* MASK
MASK deletes the generation datasets but leaves the GDG base intact and the catalog entries empty.
Common pitfalls
(+1)not rolling over after an ABEND — the in-flight generation is deleted byDISP=(NEW,CATLG,DELETE), leaving the GDG state unchanged. Re-running the job creates a fresh(+1)— no manual cleanup needed in the normal case.- Two jobs creating
(+1)simultaneously — classic GDGs serialize on the catalog entry; one job waits. Under EXTENDED mode they may both succeed and the rollover order is determined by job-completion timestamp, which is fine for parallel data-pump jobs. Within a single job, every(+1)reference resolves to the same generation; this is by design. - Forgetting
DCB=orDCB=MODEL— JCL allocates the generation withRECFM=U,LRECL=0,BLKSIZE=0and the next read with a real DCB fails. Always supply DCB attributes on(+1)directly or via model DSCB. - Referencing
(0)in a job that creates(+1)first —(0)resolves to the pre-existing generation (what was current before the job ran);(+1)resolves to the new one. After rollover at end-of-job, the new(0)becomes what was(+1). Be deliberate about which you want. LIMITreached andEMPTYset unintentionally — the next(+1)scratches every generation, not just the oldest. Always confirmNOEMPTYis set unless you genuinely want batch-replacement semantics.- NOSCRATCH leaving orphaned datasets — uncatalogued generations accumulate volume space silently. Either use
SCRATCHor schedule a cleanup job usingLISTCandDELETE. - GDG base deleted while generations exist (without FORCE) — IDCAMS rejects with RC=56. Always use
GENERATIONDATAGROUP FORCEor delete generations first with theMASKsyntax. - Crossing the 9999 generation number — JES2 wraps to
G0001V00. If an old generationG0001V00still exists (uncatalogued under NOSCRATCH), the nextG0001V00allocation conflicts on volume. Use SCRATCH or migrate to EXTENDED mode with proactive cleanup. - Catalog the model DSCB without
SPACE=(TRK,0)— a model with allocated tracks consumes real disk space and may be deleted by space-reclamation jobs. Zero-track is the correct allocation. - Confusing
(0)(current) with(1)(which means absolute generation 0001) —(1)is parsed as an absolute number, not a relative. Always include the sign for relative:(+1),(-0)(rare),(0).
Sources
References consulted while writing this article. Links open in a new tab.
- IBM Documentation — Defining Generation Data Groups (DFSMS Using Data Sets) — Authoritative reference for GDG base definitions, model DSCBs, and relative-generation semantics used in this article.
Real-world recipes
Daily rolling backup with 14-day retention
The canonical GDG use case: one IDCAMS define, one model DSCB, then a JCL job scheduled daily that produces (+1) and reads (0) for change-detection. After 14 days, the oldest generation rolls off automatically.
Step 1 — define the GDG and model (one-time):
//DEFGDG JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DEFINE GENERATIONDATAGROUP -
(NAME(ALICE.DAILY.BACKUP) -
LIMIT(14) -
SCRATCH -
NOEMPTY)
/*
//STEP2 EXEC PGM=IEFBR14
//MODELDS DD DSN=ALICE.DAILY.BACKUP.MODEL,
// DISP=(NEW,CATLG,DELETE),
// SPACE=(TRK,0),
// DCB=(RECFM=FB,LRECL=2000,BLKSIZE=27000),
// UNIT=SYSDA,
// VOL=SER=USR001
Step 2 — daily job (re-run by the scheduler):
//DAILYBKP JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP1 EXEC PGM=BACKUPPG
//SYSPRINT DD SYSOUT=*
//INPUT DD DSN=ALICE.LIVE.DATA,DISP=SHR
//PREVIOUS DD DSN=ALICE.DAILY.BACKUP(0),DISP=SHR
//OUTPUT DD DSN=ALICE.DAILY.BACKUP(+1),
// DISP=(NEW,CATLG,DELETE),
// DCB=ALICE.DAILY.BACKUP.MODEL,
// SPACE=(CYL,(50,10)),
// UNIT=SYSDA
After day 15, the first day's backup is scratched automatically; the GDG always contains the most recent 14.
Restore from a specific generation by absolute name
A user reports an issue on data from 3 days ago. List the GDG, identify the absolute name of (-3), and run a restore job that explicitly references that name (so a later rollover during the long-running restore doesn't shift relative numbers under it).
//LISTGDG JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X
//STEP1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
LISTCAT ENTRIES(ALICE.DAILY.BACKUP) ALL
/*
Identify the absolute name (say G0042V00), then run the restore:
//RESTORE JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP1 EXEC PGM=RESTOREPG
//INPUT DD DSN=ALICE.DAILY.BACKUP.G0042V00,DISP=SHR
//OUTPUT DD DSN=ALICE.LIVE.DATA,DISP=OLD
Migrate a classic GDG to EXTENDED with higher LIMIT
A monthly extract has been running with LIMIT(255) for years and is now at the catalog cap. Migrate to EXTENDED and raise the limit to 500, keeping every existing generation.
//MIGRATE JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X
//STEP1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
ALTER ALICE.MONTHLY.EXTRACT -
GDGEXTENDED(YES) -
LIMIT(500)
LISTCAT ENTRIES(ALICE.MONTHLY.EXTRACT) ALL
/*
The migration is in-place — no rebuild of generations is required.
Tear down a GDG and all its generations
End of life for an old reporting GDG: delete every generation and the base in one IDCAMS step.
//CLEANUP JOB (ACCT),'ALICE',CLASS=A,MSGCLASS=X
//STEP1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DELETE ALICE.OLD.REPORT.GDG GENERATIONDATAGROUP FORCE
/*
If only the data is obsolete but the GDG base will be reused, delete just the generations:
DELETE ALICE.OLD.REPORT.GDG.* MASK
The base survives with an empty generation list, ready for new (+1) allocations.
Recover from a NOSCRATCH leak
A GDG was running with NOSCRATCH and has accumulated 200 uncatalogued generations on a single volume. The volume is filling. Reclaim it by listing the volume's contents, identifying the orphans (uncatalogued *.GnnnnV00 matching the GDG base name pattern), and deleting them:
//STEP1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
LISTCAT LEVEL(ALICE.DAILY.BACKUP) ALL
DELETE ALICE.DAILY.BACKUP.G0001V00 NVR
DELETE ALICE.DAILY.BACKUP.G0002V00 NVR
/* repeat for each orphan ... or write a REXX to generate the DELETEs */
/*
NVR (non-VSAM uncatalogued) is the disposition for an uncatalogued non-VSAM dataset that has data on volume.
Then switch the GDG to SCRATCH to prevent future leaks:
ALTER ALICE.DAILY.BACKUP SCRATCH
A multi-step job that reads and writes the same GDG
A daily aggregation reads the previous day's snapshot and the current day's deltas, produces a new snapshot, and verifies it — all in one job. Three DDs reference the same (+1) resolution and they all land in the same physical generation.
//DAILYAGG JOB (ACCT),'ALICEJ01',CLASS=A,MSGCLASS=X,NOTIFY=&SYSUID
//STEP1 EXEC PGM=AGGPG
//SYSPRINT DD SYSOUT=*
//YESTRDY DD DSN=ALICE.SNAPSHOT.GDG(0),DISP=SHR
//DELTAS DD DSN=ALICE.LIVE.DELTAS,DISP=SHR
//TODAY DD DSN=ALICE.SNAPSHOT.GDG(+1),
// DISP=(NEW,CATLG,DELETE),
// DCB=ALICE.SNAPSHOT.MODEL,
// SPACE=(CYL,(100,20)),
// UNIT=SYSDA
//*
//STEP2 EXEC PGM=VERIFY,COND=(0,NE,STEP1)
//SYSPRINT DD SYSOUT=*
//INPUT DD DSN=ALICE.SNAPSHOT.GDG(+1),DISP=SHR
//*
//STEP3 EXEC PGM=NOTIFYPG,COND=(0,NE,STEP2)
//INPUT DD DSN=ALICE.SNAPSHOT.GDG(+1),DISP=SHR
If STEP2 or STEP3 fails (RC != 0), the next step is skipped via COND but the new generation has been created. To roll back on any failure, change the (+1) to DISP=(NEW,CATLG,DELETE) (the default) and add an explicit IF/THEN/ELSE cleanup step:
// IF (RC > 0) THEN
//CLEANUP EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DELETE ALICE.SNAPSHOT.GDG(+1)
/*
// ENDIF
The (+1) inside the cleanup IDCAMS step resolves to the just-created generation if the rollover has not happened yet. Verify behaviour for your z/OS release before relying on this pattern.
Inspect a GDG from TSO
A REXX one-liner from TSO READY mode prints the live generation list — useful for ad-hoc checks without writing JCL:
LISTC ENT('ALICE.DAILY.BACKUP') ALL
Output:
GDG BASE ------- ALICE.DAILY.BACKUP
IN-CAT --- CATALOG.USER01
ATTRIBUTES
LIMIT-----------------14 NOSCRATCH NOEMPTY
GDGEXTENDED-NO
ASSOCIATIONS
NONVSAM--ALICE.DAILY.BACKUP.G0042V00
NONVSAM--ALICE.DAILY.BACKUP.G0043V00
NONVSAM--ALICE.DAILY.BACKUP.G0044V00
...
For more programmatic inspection, route the LISTCAT output through IDCAMS in batch and parse the captured SYSPRINT with a REXX exec or workstation tool — both Zowe and the file-transfer recipes in the FTP cheat sheet are good fits.
Catalog-only deletion for compliance recovery
Sometimes retention policy requires keeping the data but removing it from the active GDG list (the generations stay catalogued under absolute names for legal hold purposes but no longer roll). Use NOSCRATCH plus ALTER to detach generations:
//STEP1 EXEC PGM=IDCAMS
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
ALTER ALICE.MONTHLY.EXTRACT NOSCRATCH
ALTER ALICE.MONTHLY.EXTRACT LIMIT(0)
/* generations remain catalogued under absolute names, GDG list empty */
ALTER ALICE.MONTHLY.EXTRACT LIMIT(12)
/* future generations roll into the GDG normally; the legal-hold generations stay catalogued *,
* but outside the GDG and unaffected by future rollovers */
/*
After this sequence, new (+1) allocations start populating a fresh window without touching the preserved older generations.