Remember when everything was going to be on the blockchain? It feels so long ago. Fast forward a few years, and now it’s nearly impossible to avoid the pressure to apply AI to everything. While some AI prompts may help you to create digital art—often to humorous effect—there are others that promise to assist with software development. “Low-code” and “no-code” had their moment, but today, everyone is looking to generative AI.
Why? People long for the ability to describe what they want in a natural language and letting the computer do the rest. Here’s the catch – we basically have already had this for decades, but I keep hearing that nobody knows it. If you read the title, you already know where I am going with this: COBOL.
Yes, that COBOL. The punchline of programming jokes. It’s old. It’s verbose. It’s almost English. Surely, if it had any good ideas, newer languages would have copied them, right? Not quite.
While it might not generate headlines, COBOL does something most languages don’t—especially when it comes to logic clarity and maintainability. In this post, I’ll walk you through one of COBOL’s most overlooked features: the 88-level field. You’ll learn:
- What 88 Levels really are (hint: they’re more than just booleans)
- How they simplify conditional logic and validation
- Real-world examples showing them in action for parsing, initialization, and conversions
- Subtle behaviors (like how values are chosen when setting)
- When to use—and when not to use—88 Levels in your COBOL code
So, if you’re a seasoned COBOL developer looking to rediscover a powerful tool—or a curious modern developer wondering why this “ancient” language still has lessons to teach—this post is for you.
Because sometimes the most innovative solution isn’t the newest one—it’s the one that’s been working all along.
What is an 88-Level Field in COBOL?
The simple answer is that it is a boolean. This is a lie.
A more accurate answer is that an 88-level field is a label or descriptor of a field to check when a certain condition is met.
Consider the following data definition:
01 MY-FIELD PIC X VALUE SPACE.
This extremely simple field can hold a single alphanumeric character, and it is initialized to a space. Other data can be moved into this field, possibly from user input or perhaps a file. Should we choose to make a comparison of this field’s contents, we could use IF
statements that directly compare against specific values.
IF MY-FIELD = 'Y' OR MY-FIELD = 'N' THEN
DISPLAY 'WE HAVE VALID DATA'
IF MY-FIELD = 'Y' THEN
DISPLAY 'POSITIVE'
ELSE
DISPLAY 'NEGATIVE'
END-IF
ELSE
DISPLAY 'INVALID DATA FOUND IN MY-FIELD'
END-IF.
This works, but it could be better. Let’s look at what this could be with 88 Levels.
01 MY-FIELD PIC X VALUE SPACE.
88 MY-FIELD-IS-VALID VALUE 'Y' 'N'.
88 MY-FIELD-IS-POSITIVE VALUE 'Y'.
88 MY-FIELD-IS-NEGATIVE VALUE 'N'.
…
IF MY-FIELD-IS-VALID THEN
DISPLAY 'WE HAVE VALID DATA'
IF MY-FIELD-IS-POSITIVE THEN
DISPLAY 'POSITIVE'
ELSE
DISPLAY 'NEGATIVE'
END-IF
ELSE
DISPLAY 'INVALID DATA FOUND IN MY-FIELD'
END-IF.
At first, this might appear to be moving hard-coded values out of the program logic.
Yes, that is happening, but it is also simplifying how we describe the data.
With MY-FIELD-IS-VALID
, we can list our valid fields and quickly describe possible values that would make it true. If more values need to be considered valid, we can simply add them to the list of possible values. In the following data definition, we are adding the plus and minus signs to possible positive and negative values, and the logic for the control flow does not need to be updated!
01 MY-FIELD PIC X VALUE SPACE.
88 MY-FIELD-IS-VALID VALUE 'Y' '+' 'N' '-'.
88 MY-FIELD-IS-POSITIVE VALUE 'Y' '+'.
88 MY-FIELD-IS-NEGATIVE VALUE 'N' '-'.
Why Use 88-Level Fields?
In short, you would want to use 88-level fields in an effort to:
- Improve readability
- Help enforce business rules
- Make condition checking more expressive
Real-World Examples of COBOL 88-Level Usage
Of course, there are more reasons to use these 88 Level labels than checking for truthiness. One common use is to set a field to a programmed value.
Let’s look at a different data description example:
01 SOME-BUFFER PIC X(7).
88 SOME-BUFFER-DEFAULTS VALUE ' 00'.
01 RECORD-TYPES REDEFINES SOME-BUFFER.
05 RECORD-HEADER PIC X.
88 RECORD-TYPE-IS-VALID VALUE 'A' 'B'.
88 RECORD-TYPE-IS-A VALUE 'A'.
88 RECORD-TYPE-IS-B VALUE 'B'.
05 RECORD-BODY.
10 COMMON-FIELDS.
15 COOL-FACTOR PIC 99.
88 COOL-FACTOR-IS-VALID VALUES ZERO THRU 10.
88 COOL-FACTOR-IS-ZERO VALUE ZERO.
88 COOL-FACTOR-IS-LOW VALUES 01 THRU 04.
88 COOL-FACTOR-IS-MEDIUM VALUES 05 THRU 07.
88 COOL-FACTOR-IS-HIGH VALUES 10 08 THRU 10.
10 RECORD-SPECIFIC-FIELDS PIC X(04).
10 RECORD-TYPE-A REDEFINES RECORD-SPECIFIC-FIELDS.
15 REC-A-YEAR PIC 9(02).
88 REC-A-YEAR-IS-19XX VALUES 70 THRU 99.
15 FILLER PIC X(02).
10 RECORD-TYPE-B REDEFINES RECORD-SPECIFIC-FIELDS.
15 REC-B-YEAR PIC 9(04).
15 REC-B-YEAR-SPLIT REDEFINES REC-B-YEAR.
20 REC-B-YEAR-CC PIC X(02).
88 REC-B-YEAR-IS-19XX VALUE 19.
88 REC-B-YEAR-IS-20XX VALUE 20.
20 REC-B-YEAR-YY PIC X(02).
Side note! I intentionally used more verbose field names to focus on the 88-level concepts. Don’t let that scare you. There are ways to effectively namespace your fields, and I highly recommend doing that rather than prefixing them with “REC-A-” or “REC-B-”.
Whew! That’s a lot of text, but I tried to provide a good, versatile example.
Let’s break it down.
SOME-BUFFER
is a basic alpha-numeric field.- If we want to reset this during processing, we could do so with
SET SOME-BUFFER-DEFAULTS TO TRUE
and the data will be zeroed out to the value inSOME-BUFFER-DEFAULTS
.
This is something that would make sense to do between processing records of an input file, for example. I chose to redefine this block of memory so that we have a header and body to consider. The header simply holds the record type, either ‘A’ or ‘B’ — much in the same way I presented the positive and negative examples above.
- The record body has two numeric characters for
COOL-FACTOR
followed by four bytes that depend entirely on the type of record we are dealing with. - In this scenario, “A” records have 2-digit years, and “B” records have 4-digit years.
Coding is more fun if we pretend that we are reliving the height of the Y2K scare and need to convert data to save everyone’s bank accounts from being wiped. 🙂
Should we find ourselves processing a file with record type A, we can perform the conversion with the help of our 88 Levels like so:
MOVE REC-A-YEAR TO REC-B-YEAR.
IF REC-A-YEAR-IS-19XX THEN
SET REC-B-YEAR-IS-19XX TO TRUE
ELSE
SET REC-B-YEAR-IS-20XX TO TRUE
END-IF.
SET RECORD-TYPE-IS-B TO TRUE.
Readers may note that the method we use to determine whether the converted date should be in the 1900s or the 2000s is solely based on an arbitrary cutoff. Personally, I dislike this style of determining the century to set, but it was good enough for Microsoft back in the day, and they are still in business, so we’ll pretend that this is okay. In the future, when the next batch of conversions are run, we can push the lower limit up or even deprecate “A” records completely so we don’t have to worry about it anymore.
How to Use Value Ranges in COBOL 88-Level Fields
Let’s take another look at one of those 88 Levels for COOL-FACTOR
.
88 COOL-FACTOR-IS-HIGH VALUES 10 08 THRU 10.
10 08 THRU 10
…What is that even supposed to mean?
Welp, that right there is consistency with a preference.
When a range of values is conditionally true for an 88 Level, we can use the syntax of THRU , inclusive. Sure, I could have listed every value once without duplication in fewer characters, but I wanted to bring up a cool little trick. Not only do we care about the range, we also care about the value that is set when we say SET COOL-FACTOR-IS-HIGH TO TRUE
.
I no longer have access to a mainframe to verify the behavior of more “professional” COBOL compilers, but at least for GNU COBOL, it’s the first listed value that is loaded into the associated field. By making sure that 10
is the first value, we won’t have the awkward situation where 08
is loaded unless it was deliberately put there.
When to Use 88-Levels
So, when would anybody use this? It’s not as if COBOL is being used in many new projects, and I certainly don’t expect people to start just because of this article.
In a previous development project, I was able to make use of tables and 88-levels together to loop through multiple data sources for the OPEN
and CLOSE
actions – flat files, VSAMs, database queries … it didn’t matter. And the magic of the simple, conditional 88-levels made it a pleasure to use.
Outside of this experience, there are a number of good opportunities to use 88-levels.
As a named conditional, 88-levels can help if you find yourself needing to check against known good values. This could be for things like validation, argument parsing, and tokenization just to name a few scenarios. In testing the above code for year conversions, I used one to know when I reached a maximum value.
There is also the situation where a conditional block is convoluted due to many fields and values being listed. You may be able to make sense of the individual parts, but the reasoning behind it could be obscured. By using an 88-level, you can relay the intent of the condition with its name alone. This would be akin to calling a boolean function that determines its result by checking the state of multiple variables, except it can be more automatic and without additional stackframes.
For times when you are setting values instead of checking them, 88-levels can be a significant time-saver in initializing fields to preset data during processing. You might have a group of Yes/No flags that need to be set in a particular way, like DIP switches. Name the allowable configurations, and call it a day. When you need to use one of these configurations, simply setting the 88-level to ‘TRUE’ will put the appropriate values in place.
These are just a few situations where the 88 Levels could make your life easier. “Magic” numbers, hard-coded values, and nearly anything you would put in a constant in other languages are good candidates for being used as 88-levels, but be careful not to go overboard with this. Sometimes, just labeling a value as its own field actually is better. Play around, and see what works – as long as the intent of your code is clear, it’ll be alright.
Conclusion
COBOL might not be the fanciest or most exciting language, but that is one of the reasons I think people should spend more time with it. New and shiny tech might be fun to play with, but proven older technologies can still provide value.
I hope I was able to demonstrate how a small, nearly forgotten feature can be utilized to simplify logic in your code branches as well as set trusted values in your data. If you’re looking for more insights on modernization, check out more posts on the Keyhole Software Employee Blog.