James Allman | JA Technology Solutions LLC
Reading COBOL Copybooks: The Map Behind Mainframe Record Layouts
A mainframe flat file is just bytes. The COBOL copybook is the map that says where each field is, what type it holds, and how many decimal places the number carries. This article covers the copybook syntax you encounter most often and what it means for an ETL, migration, or integration developer parsing those bytes.
When an ETL team receives a flat-file extract from a mainframe or IBM i system, they get two things: the file, and (if they are lucky) a COBOL copybook. The file is bytes. Without the copybook, those bytes have no structure: no field names, no delimiters, no type labels. The copybook is the map that says where each field starts, how long it is, what type it contains, and how numeric fields encode their decimal position. Without it, you cannot parse a single field correctly.
This article is the third piece in a trilogy on mainframe data. My earlier piece on EBCDIC and ASCII conversion covers how character bytes are encoded. The packed-decimal article, reading mainframe numbers correctly, covers how numeric bytes are encoded. This article covers what a copybook actually says, section by section, so you can read one on a real project without getting stuck on the syntax.
I have worked with COBOL copybooks in IBM i and mainframe contexts for over 35 years: reading them to build parsers, converting them to SQL DDL for target schemas, and using them to locate bugs in extraction code that was silently computing wrong values.
What a copybook is
A COBOL copybook is a source member that contains data structure definitions. It is not a program and does not run on its own. Programs include it using the COPY statement: COPY CUSTOMER-REC. inserts the copybook's text at that point in the program, exactly as if the developer had typed it inline. This is how a single record layout gets shared across dozens of programs that read or write the same file. When the layout changes, the copybook changes, and every program that copies it picks up the new definition on the next compile.
On IBM i (AS/400), copybooks typically live in source physical files named QCPYSRC or similar, with a member name that matches the record format. On z/OS mainframes, they live in partitioned datasets (libraries) with a member name. The copybook you receive for an extract file describes exactly one thing: the byte layout of a record. That is its entire purpose.
The COBOL Copybook Explorer parses a copybook and displays the computed byte offset, length, type, and scale for every field, which removes the arithmetic from the setup work. You can paste a copybook directly and see the layout table immediately.
Level numbers: groups and fields
Every item in a COBOL copybook begins with a level number. Level 01 is the record itself, the top of the hierarchy. Levels 02 through 49 are subordinate items. A group item at level 05 contains everything at level 10 (or higher) that follows it, until the next item at level 05 or lower. Only leaf items (items with no children) actually occupy bytes in the record. Group items are structural containers: they give a name to a range of bytes and let you reference that range as a unit in program logic, but they have no PIC clause of their own.
Two special level numbers exist outside that hierarchy. Level 66 is a RENAMES clause that assigns an alias to a range of fields. Level 77 is a standalone working-storage item with no group relationship. Level 88 is a condition name: a named true/false test for specific values of the field above it (88 STATUS-ACTIVE VALUE 'A'.). Level 88 entries consume no bytes; they are metadata about the parent field's values.
The practical consequence for a parser is that group levels tell you nothing about byte widths directly. To compute the byte offset of a field, you walk the flattened list of leaf fields in order, accumulating their widths. Only then do you know where field N starts. This is the arithmetic the COBOL Copybook Explorer handles for you: it flattens the hierarchy, computes every offset, and shows the byte range for each leaf field.
PIC clauses: what each field contains and how wide it is
The PICTURE (PIC) clause defines a field's type and size. The most common patterns you will encounter are: PIC X(n) for alphanumeric (character) data, n bytes wide; PIC 9(n) for unsigned numeric data with n digits; PIC S9(n) for signed numeric data; and PIC S9(n)V99 for signed numeric data with an implied decimal point, in this example two digits to the right of it. The V character marks the implied decimal position. It takes up no bytes in the record. The value 123.45 stored as PIC S9(5)V99 is physically the same bytes as the integer 12345; the decimal position is metadata from the PIC clause, not something in the data.
Width formulas differ by type. For PIC X(n), the byte width is n. For PIC 9(n) or PIC S9(n) with DISPLAY usage, the width is also n (one byte per digit in zoned decimal). For COMP-3 usage, the width is ceil((n + 1) / 2) where n is the total digit count including both sides of the implied decimal point: a PIC S9(7)V99 COMP-3 field has 9 digits total and occupies 5 bytes. For COMP or COMP-4 (binary), the width is 2 bytes for up to 4 digits, 4 bytes for 5 through 9 digits, and 8 bytes for 10 through 18 digits.
Repeated characters shorten the notation. PIC X(10) is identical to PIC XXXXXXXXXX. PIC 9(5)V99 is identical to PIC 9999V99. You may encounter either form in real copybooks.
USAGE: how the bytes are physically stored
The USAGE clause controls how the bytes of a field are physically laid out. When omitted, USAGE defaults to DISPLAY: one EBCDIC byte per digit for numeric fields, or one EBCDIC byte per character for alphanumeric fields. USAGE COMP-3 (or COMPUTATIONAL-3) switches a numeric field to packed decimal: two digits per byte, sign in the trailing nibble. USAGE COMP or COMP-4 switches to binary (big-endian two's complement): 2 bytes for fields up to PIC 9(4), 4 bytes for up to PIC 9(9), and 8 bytes for up to PIC 9(18).
The same PIC clause produces a completely different byte width depending on USAGE. A PIC S9(7)V99 field (9 total digits) is 9 bytes as DISPLAY, 5 bytes as COMP-3, and 4 bytes as COMP. If your code uses the wrong USAGE to compute the field's width, every field after it in the record is at the wrong offset. The error is silent: no exception, no warning, just wrong values across every field that follows.
USAGE can appear on the field itself or on a group item. When it appears on a group item, every subordinate leaf field inherits it. A group declared with USAGE COMP-3 means all its children are packed decimal unless a child overrides with its own USAGE. This is a common source of confusion when reading copybooks for the first time.
OCCURS: fixed and variable-length arrays
OCCURS n TIMES declares that a field or group repeats n times consecutively. A declaration like 05 STORE-SALES OCCURS 52 TIMES PIC S9(9)V99 COMP-3. says there are 52 consecutive 5-byte packed decimal fields, one per week. The total width of this element is 52 times 5 bytes, or 260 bytes. To access element k (1-based in COBOL, 0-based in most modern code), the byte offset is the group's base offset plus (k - 1) times the element width.
OCCURS DEPENDING ON is the variable-length variant. The repeat count comes from another field earlier in the record rather than being fixed in the copybook. A canonical example: 05 LINE-ITEM OCCURS 1 TO 50 TIMES DEPENDING ON LINE-COUNT PIC S9(7)V99 COMP-3. Here, LINE-COUNT holds the actual repeat count for each record. For a fixed-width file, variable-length arrays mean that record lengths differ across records: record A with 3 line items is shorter than record B with 30. Naive parsers that assume a constant record length will fail on files with DEPENDING ON arrays.
Both OCCURS forms break the assumption that you can compute every field's offset at parse-design time. For DEPENDING ON, the offset of any field that follows the array depends on the count stored in the current record. You must read that count from each record before you can locate the fields that come after the array.
REDEFINES: two layouts over the same bytes
REDEFINES tells the compiler that a new name covers the same bytes as an earlier item. It is the mainframe equivalent of a C union. A common pattern: a header record type and a detail record type share the same fixed-width record. A type-code field in the first two bytes identifies which layout applies. The copybook defines one 01-level group and then uses REDEFINES to give alternate interpretations to the rest of the record depending on the type code. In a flat file, every physical record is the same number of bytes regardless of which REDEFINES branch is active.
REDEFINES frequently appears inside groups as well. A date field might be defined as a single PIC 9(8) field, then redefined as a group of three subfields (PIC 9(4) year, PIC 9(2) month, PIC 9(2) day) for program convenience. Both names occupy exactly the same 8 bytes. A parser that reads those bytes once can apply either layout.
The practical consequence for ETL code: if a file contains multiple record types distinguished by a type code, you need to check the type code on each record before choosing which field layout to apply. A parser that ignores the type code and always applies the first layout will produce plausible-looking but wrong values for any record of a different type. The COBOL Copybook Explorer shows REDEFINES relationships in the layout view, so you can see which field names share bytes before writing any parse logic.
How the copybook connects to EBCDIC and packed decimal
The copybook tells you which bytes to apply which decoding to. Without it, you are guessing. A PIC X(n) field with DISPLAY usage holds EBCDIC character data: apply the EBCDIC-to-ASCII conversion covered in my EBCDIC conversion article, using the source system's code page. A PIC 9(n) or PIC S9(n) field with DISPLAY usage holds zoned decimal: the high nibble of each byte is a zone nibble (normally 0xF), the low nibble is the digit value, and the zone nibble of the last byte carries the sign. A PIC S9(n) field with COMP-3 usage holds packed decimal: two digits per byte, sign in the trailing nibble. All three of these formats appear in the same record. None of them are self-describing.
The implied decimal point (the V in a PIC clause) applies to both zoned and packed fields. After you decode the digits from the raw bytes, you shift the result right by the number of decimal places the V specifies. A packed field decoded as the integer 10012003 with a V99 clause represents the value 100120.03. Miss this step and every monetary value in the extract is off by a power of ten. My article on reading mainframe numbers covers the scale error and how to catch it during validation.
The tools that handle each decoding layer: the EBCDIC to ASCII Converter for character fields, the Packed Decimal / COMP-3 Converter for numeric fields, and the COBOL Copybook Explorer for the layout itself. The Fixed-Width to CSV Converter handles the final step of applying a field schema to slice a flat file into named columns.
What breaks naive parsers
The failures that come up most often in code I review or debug are: applying EBCDIC-to-ASCII conversion to the entire record including packed decimal bytes (the packed bytes are not text and must not be converted as text); computing field offsets without accounting for COMP-3 byte widths (a nine-digit COMP-3 field is 5 bytes, not 9); ignoring REDEFINES and always applying the first field layout even when the type code says otherwise; and treating OCCURS DEPENDING ON arrays as if they have a fixed repeat count, which shifts every subsequent field's offset for records where the count differs from the expected value.
Scale errors are the quietest failure mode. If you decode the nibbles correctly but do not shift for the implied decimal, a field declared as PIC S9(5)V99 comes out as an integer 100 times too large. The magnitude is consistent across all records, so downstream range checks may not catch it. This is one of the things I verify first when reviewing extraction code for a mainframe migration project.
The combination of REDEFINES and OCCURS is the hardest case. A variable-length array followed by REDEFINES means that the byte offset of fields after the array varies by record, and the correct field layout depends on a type code. Getting this right requires reading both the count and the type code from each record before locating any subsequent fields. Off-the-shelf conversion tools rarely handle this; it needs purpose-built parsing logic.
Working with a copybook on a real project
Before writing any extraction code, read the full copybook. Walk through every 01-level group and identify: the record types (if multiple REDEFINES branches exist at the top level), the leaf fields in each branch with their PIC clause and USAGE, every OCCURS clause and whether it has a DEPENDING ON, and every level 88 that describes meaningful values for a field you care about. Make a flat table: field name, byte start, byte length, type (EBCDIC text, zoned decimal, packed decimal, binary), digit count, decimal places.
Once that table exists, computing byte offsets is arithmetic, and writing the parser is mechanical. The mistakes come from skipping this step: starting with code before reading the copybook carefully, or reading the copybook partially and missing a REDEFINES that applies to a field your code touches.
For projects where the copybook is missing, IBM i's DSPFFD command (Display File Field Description) produces a field-level listing for any database file, including type, length, and decimal positions in a format you can read without COBOL knowledge. On z/OS, the COBOL compiler can produce a listing that includes the resolved copybook text with level numbers, field names, and byte offsets. Either is a substitute for a copybook when none is available. If you are dealing with a flat-file extract for which no layout document exists at all, that is the point at which to involve someone with hands-on mainframe experience: guessing the layout from hex dumps is possible but slow, and errors compound.
Tools for copybook work
The COBOL Copybook Explorer is the primary tool for this work. Paste a copybook and it produces a flat layout table with byte offsets, lengths, types, and decimal positions for every leaf field. It handles level-number nesting, REDEFINES relationships, OCCURS clauses, COMP-3, COMP/BINARY, and level 88 conditions. The SQL generation view produces a CREATE TABLE statement in five dialects with appropriate column types for each COBOL type. The sample-data decoder applies the layout to raw records, displaying decoded values alongside their hex bytes.
For the character encoding layer: the EBCDIC to ASCII Converter handles individual EBCDIC bytes or text blocks with code page selection (CP037, CP500, CP1047, and others). For numeric fields: the Packed Decimal / COMP-3 Converter decodes packed and zoned decimal from hex input and shows the nibble-by-nibble layout, the sign, and the decimal result. For applying a field schema to a flat file: the Fixed-Width to CSV Converter takes column definitions (name, start, length) and produces CSV output.
These tools handle inspection, debugging, and small test cases well. Production pipelines for large files, multi-record-type layouts, variable-length OCCURS, and complex REDEFINES structures need purpose-built code. The tools are most useful during the design and validation phases, for verifying your offset table against a few known records before processing a full production extract. If you are building an extraction pipeline for a mainframe migration or integration project and the copybook complexity is beyond what the tools can handle alone, IBM i and mainframe consulting is where that work typically fits, or Ask James to describe the file structure and what you need out of it.