August 24, 2026
One of the things I enjoy most about procedure-driven RPG is that it encourages us to think about responsibility. Every procedure should have a clear purpose. It should perform one task well and leave unrelated concerns to other parts of the application.
That sounds straightforward enough, yet one responsibility often finds its way into nearly every procedure we write: failure handling.
If you have worked with RPG for any length of time, you have probably encountered applications where nearly every procedure begins with a MONITOR operation. The procedure performs its work, catches any exception that occurs, logs an error, returns an indicator or status code, and continues.
There is nothing inherently wrong with that approach. The MONITOR operation is one of RPG’s most useful language features, and there are many situations where it is exactly the right tool for the job.
The question isn’t whether a procedure can handle its own failures. The question is whether it is the best place to decide what should happen when something goes wrong.
Consider the following procedure:
dcl-proc Customer_Save export; monitor; // Save the customer record SaveCustomerRecord(customer); return *on; on-error; Error_Log(); return *off; endmon; end-proc;
At first glance, the procedure appears perfectly reasonable. It saves a customer and reports whether the operation succeeded.
Looking a little closer, however, reveals that it is actually performing three different responsibilities. It saves a customer, decides how exceptions should be handled, and decides how those exceptions should be logged. While those responsibilities often appear together, they are not necessarily related.
Suppose your organization decides that every application error should be written to a database table instead of a spool file. Or perhaps operations want every failure forwarded to an enterprise monitoring solution. If every service procedure contains its own logging logic, those changes could affect hundreds of source members.
And let’s be honest, how likely is your department to approve a project whose sole purpose is modifying 500 or 600 programs just to change how failures are logged?
Yes, AI-assisted development tools can help reduce the amount of manual effort required to make those changes. They can help locate code, generate modifications, and accelerate development. But they do not eliminate the need to test those changes, coordinate deployments, or manage the impact of modifying hundreds of objects.
Good architecture still matters because it reduces the number of objects that need to change in the first place.
The problem isn’t the MONITOR operation. The problem is that responsibility for failure handling has become scattered throughout the application.
One question I often ask while designing a procedure is this: “If this procedure disappeared tomorrow, what capability would the application lose?”
If Customer_Save() disappeared, the application should lose its ability to save customers. It shouldn’t also lose its ability to log failures. Those are separate capabilities, and they deserve separate homes within the application.
This leads to another important distinction: not every failure is an exception.
Suppose a customer number cannot be found. Depending on the application, that may not represent an error at all. It may simply mean the user entered an invalid customer number. Likewise, a validation routine may reject an order because a required field is missing. Those are business outcomes. The caller should expect them and decide how to respond.
Unexpected failures belong in a different category. A called procedure sends an escape message. An array index falls outside its valid range. A decimal data error occurs. These situations represent conditions that the application did not anticipate during normal processing.
Embedded SQL provides another example. A failed SQL statement does not normally trigger an RPG MONITOR block. Instead, SQL reports the condition through SQLSTATE, SQLCODE, and diagnostic information. Those conditions still need to be considered, but they represent a different type of failure from an RPG exception.
Treating every failure as though it were an exception often leads to procedures filled with MONITOR blocks, logging code, and status indicators, making the business logic progressively more difficult to follow.
A common mistake is treating every unsuccessful operation as something exceptional. Sometimes the operation completed exactly as designed; it simply produced a result that the caller needs to consider.
Consider an order creation process. A customer may have passed validation, but their credit limit may not allow the order to proceed. Inventory may not be available. A required approval may still be pending.
None of those situations represent a programming failure. They are expected outcomes of the business process.
Instead of forcing those situations through exception handling, the procedure can communicate the result directly.
dcl-ds OrderResult qualified; Success ind; CreditHold ind; InventoryUnavailable ind; ApprovalRequired ind; end-ds; OrderResult = Order_Create(order); select; when OrderResult.Success; SendConfirmation(); when OrderResult.CreditHold; NotifyCreditDepartment(); when OrderResult.InventoryUnavailable; CreateBackorder(); when OrderResult.ApprovalRequired; RouteForApproval(); other; Error_Log(); endsl;
The procedure creating the order does not need to know what the application should do with each outcome. Its responsibility is to determine the result and communicate it clearly.
The workflow, on the other hand, understands the larger business process and can decide how to respond.
Now consider the same application flow when we separate business outcomes from unexpected failures.
monitor; Customer_Validate(customer); Customer_Save(customer); Customer_EmailWelcome(customer); on-error; Error_Log(); endmon;
The service procedures now concentrate entirely on the work they were written to perform. Validation validates. Saving saves. Sending the welcome email sends the welcome email. The workflow coordinates those operations and determines what should happen if one of them cannot complete successfully.
This approach also creates a natural place for centralized logging. Rather than every procedure deciding what information should be recorded, an error service can consistently gather the call stack, message information, job details, timestamps, and any other diagnostics your organization requires.
If those requirements change in the future, the modification is made in one place instead of throughout the application.
None of this suggests that MONITOR belongs only in the highest-level procedure. There are certainly situations where a lower-level procedure understands an exception well enough to recover from it and continue processing. In those cases, handling the exception locally is often the right design.
The important question is whether the procedure truly owns that decision. If it can recover because it understands the operation it is performing, handling the exception locally makes sense. If it cannot, allowing the caller to determine how to proceed usually produces code that is easier to understand and easier to maintain.
Like many aspects of software design, there is no rule that fits every situation. The goal isn’t to eliminate MONITOR. The goal is to place responsibility where it belongs.
A procedure that retrieves a customer should retrieve customers. A procedure that calculates shipping charges should calculate shipping charges. A service responsible for logging failures should log failures.
Good architecture isn’t measured by how many procedures an application contains or how many service programs have been created. It is measured by how little of the application has to change when the requirements do.
Failures are a normal part of software. The goal is not to pretend they will never happen. The goal is to ensure that each failure is communicated to the part of the application that has enough context to make the right decision.
When every component focuses on its primary responsibility, the application becomes easier to understand, easier to test, and easier to evolve.
Until next time, happy coding.
Gregory Simmons is a Project Manager with PC Richard & Son. He started on the IBM i platform in 1994, graduated with a degree in Computer Information Systems in 1997 and has been working on the OS/400 and IBM i platform ever since. He has been a registered instructor with the IBM Academic Initiative since 2007, an IBM Champion and holds a COMMON Application Developer certification. When he’s not trying to figure out how to speed up legacy programs, he enjoys speaking at technical conferences, running, backpacking, hunting, and fishing.
RELATED STORIES
Guru: Beyond Three-Part Naming – Running SQL Across Remote IBM i Systems
Guru: Finding Data In The Forest – Exploring Three-Part Naming In SQL
Guru: SQL Sequences In RPG Let Db2 Handle The Counting
Guru: IBM i Job Log Detective Brings Structure To Job Log Analysis In VS Code
Guru: Managing The Lifecycle Of Your Service Programs – Updates Without Chaos
Guru: Are Binding Directories A Shortcut Or A Source Of Chaos?
Guru: Service Programs And Activation Groups – Design Decisions That Matter
Guru: Binder Source Is Your Service Program’s Owner’s Manual
Guru: Access Client Solutions 1.1.9.11 – Security First, With Continued Investment In SQL Tooling
Guru: Taming The CRTSRVPGM Command – Options That Can Save Your Sanity
Guru: CRTSRVPGM Parameters That Can Save or Sink You
Guru: A First Look at Bob, The IBM i Assistant That’s Closer Than You Think
Bob More Than Just A Code Assistant, IBM i Chief Architect Will Says
IBM Pulls The Curtain Back A Smidge On Project Bob
Big Blue Converges IBM i RPG And System Z COBOL Code Assistants Into “Project Bob”
Guru: When Attention Turns To You – Writing Your Own ATTN Program
Guru: WCA4i And Granite – Because You’ve Got Bigger Things To Build
Guru: When Procedure Driven RPG Really Works
Guru: Unlocking The Power Of %CONCAT And %CONCATARR In RPG
Guru: AI Pair Programming In RPG With Continue
Guru: AI Pair Programming In RPG With GitHub Copilot
Guru: RPG Receives Enumerator Operator
Guru: RPG Select Operation Gets Some Sweet Upgrades
Guru: Growing A More Productive Team With Procedure Driven RPG
Guru: With Procedure Driven RPG, Be Precise With Options(*Exact)
Guru: Testing URLs With HTTP_GET_VERBOSE
Guru: Fooling Around With SQL And RPG
Guru: Procedure Driven RPG And Adopting The Pillars Of Object-Oriented Programming
Guru: Getting Started With The Code 4 i Extension Within VS Code
Guru: Procedure Driven RPG Means Keeping Your Variables Local
Guru: Procedure Driven RPG With Linear-Main Programs
Guru: Speeding Up RPG By Reducing I/O Operations, Part 2
Guru: Speeding Up RPG By Reducing I/O Operations, Part 1
Guru: Watch Out For This Pitfall When Working With Integer Columns











