Abstract
This publication covers modern IBM i development practices including RPG modernization with free-format syntax and ILE modularity, data-centric programming with SQL table definition and database design, database re-engineering strategies, character encoding and CCSID fundamentals, test-driven development with automation, and performance optimization tools for Db2 for i. The content addresses developers and technical professionals working with IBM i systems, providing practical guidance on modernizing legacy applications, implementing data-centric architectures, ensuring proper character encoding, establishing automated testing frameworks, and optimizing database performance through systematic analysis and index management.
Authors
Tim Rowe, Brad Bentley, Steve Bradshaw, Jim Buck, Andrew Clark, Andrew Youens, Scott Forstie, Ludovic Gasc, Shankar Giduturi, Wim Jongman, Sébastien Julliand, Gaurav Kumar, Marius le Roux, Benoit Marolleau, Kent Milligan, Ryan Moeller, Rahul Pareek, Het Patel, Michael Pavlak, Pascal Polverini, Marina Schwenk, Vikas Sonwal, Todd Stewart, Jeff Ticker, Khushi Wadhwa, Carol Woodbury, Jia Tian Zhong and George Van Eaton
- Authors and Contributors
- An introduction to modernizing IBM i applications
- Path to modernization
- Modern architecture
- Development tools
- Interfacing
- User interface
- Modern RPG
- What defines modern RPG?
- What is the business value of modern RPG?
- How do I transition to modern RPG?
- What is ILE modularization?
- What are RPG program design best practices?
- What are RPG coding best practices.
- How do I measure and reduce complexity?
- What is cyclomatic complexity?
- What is dead code and why remove it?
- How do I modularize RPG programs?
- Why Use Fully Free Format RPG
- How do I write fully free format RPG?
- How do I convert to free format?
- How do I use embedded SQL in RPG programs?
- How do I use modern RPG data structures and arrays
- How do I process JSONs within RPGLE programs?
- Data-centric development
- Database re-engineering
- Open source
- Web serving
- Security
- Globalization
- Source control
- DevOps
- AI and IBM Bob
- Test-driven development with Automation
- Performance
Modern RPG
What defines modern RPG?
Modern RPG represents the current, preferred way IBM i developers write, organize, and maintain RPG programs to keep applications reliable, readable, and adaptable. It builds on the RPG IV foundation while incorporating free-format syntax, ILE modularity, data-centric techniques, and clear coding practices that align with how professionals work in 2026. These characteristics separate maintainable, future-ready code from legacy patterns that increase defects and slow down enhancements. The following topics are described in this section:
- Core language and syntax foundation
- Free-format presentation
- ILE modularity and organization
- Data-centric practices and built-in functions
- Coding style and readability
- Interface and integration support
- Modernization spectrum and incremental approach
- Role of AI-assisted tools in 2026
Core language and syntax foundation
Modern RPG requires the RPGLE source type (or FREE for fully free-format members since IBM i 7.1). Older member types such as RPG, RPG36, or RPG38 lack subprocedures, service programs, qualified data structures, and most built-in functions introduced since V5R1. Without these features, developers stay trapped in outdated patterns that raise defect rates and make routine maintenance unnecessarily difficult.
Free-format presentation
Free-format syntax eliminates the rigid column positions that made older RPG source error-prone and hard to read. With the FREE directive (IBM i 7.1 and later), the entire member starts in column 1, allowing natural indentation, longer descriptive names, and complex expressions without alignment issues. Shops that adopt free format experience fewer entry mistakes, faster code reviews, and easier comprehension by team members.
ILE modularity and organization
Modularity through the Integrated Language Environment (ILE) provides the structural foundation for modern RPG applications. Developers extract reusable business logic—such as pricing rules, tax calculations, customer validation, and inventory checks—into service programs that export procedures. These procedures bind directly into any program or other service program that requires them, eliminating the need to duplicate logic across standalone programs or copy source code between members.
In a typical IBM i shop, ILE modularity delivers concrete, day-to-day advantages:
- Single source of truth for business rules. A change to a pricing formula, discount logic, or validation rule occurs in one procedure and immediately affects every caller—no searching dozens of programs to locate every instance.
- Reuse across multiple interfaces without modification. The same CalcTax procedure serves a 5250 order-entry screen, a REST API endpoint, a mobile app service, a batch update job, or an integration with an external system.
- Focused and reliable unit testing. Individual procedures can be tested in isolation using simple test harnesses or modern unit-test frameworks, rather than requiring a full program cycle with specific input files and database states.
- Better dependency management and isolation. Activation groups and binding directories control resource sharing and prevent the side effects (unintended global variable changes) that plague monolithic programs.
- Lower risk and faster evolution. When business requirements change—adding a new discount type, compliance check, or rounding rule—the update happens once in the service program, shrinking testing scope, deployment effort, and the chance of inconsistent behavior across the application.
Within individual modules, subprocedures replace traditional subroutines for internal code organization. Subprocedures offer:
- Parameter passing by value or reference
- Local variables with proper scope (no accidental global interference)
- Structured control flow with meaningful return values
- Clearer debugging because behavior depends only on inputs and local state
These capabilities remove many longstanding problems with EXSR and global variables, such as unexpected side effects, hard-to-trace bugs, and fragile code that breaks when one part changes.
Data-centric practices and built-in functions
Modern RPG favors database strengths and built-in functions to handle data efficiently. Developers use embedded SQL for result-set processing, multi-table joins, and aggregates when DB2 optimization provides clear advantages over native READ/CHAIN loops. Built-in functions replace custom routines for routine tasks:
- String handling: %TRIM, %REPLACE, %SCAN, %XLATE
- Date/time operations: %DATE, %DIFF, %SUBDT, %TIMESTAMP
- JSON/XML processing: DATA-INTO and DATA-GEN (since IBM i 7.3)
Qualified data structures (LIKEDS, LIKEREC) and dynamic arrays (%ALLOC, %REALLOC) produce clearer, less error-prone code than positional fields or fixed-length limits. These techniques cut code volume, improve performance through indexes and constraints, and bring RPG closer to industry-standard data practices.
Coding style and readability
Modern coding style makes code self-explanatory. Developers use descriptive names (OrderTotal, CustNumber instead of OTOT, CUSNBR), qualify subfields (CustRec.Name instead of CUSTNM), group logic with blank lines and block comments, and replace numbered indicators with named indicators or direct conditions. Consistent indentation and logical flow reduce mental effort during maintenance and debugging, especially in mixed teams that include developers familiar with other languages.
Interface and integration support
Modern RPG supports multi-channel delivery through RPG Open Access handlers and service-oriented design. Open Access enables custom I/O for browser, mobile, or API front ends while preserving core business logic. RESTful services built around ILE procedures expose rules externally without duplication. This approach extends the life of existing applications and enables new delivery channels.
Modernization spectrum and incremental approach
Modern RPG exists on a practical continuum. A program can be meaningfully modern even if it retains some native I/O, provided it uses free-format logic, extracts reusable procedures, and avoids numbered indicators. The recommended path begins with small, high-value steps:
- Convert calculations to free format
- Extract internal subprocedures from subroutines
- Move shared logic into service programs
- Replace custom routines with built-in functions
- Adopt embedded SQL for complex data access
Each step improves readability, reduces defects, and simplifies future work.
Role of AI-assisted tools in 2026
In 2026, AI-assisted tools such as IBM Bob analyze RPG source in real time within editors. They suggest replacements for outdated constructs (MOVE to assignment, indicator logic to structured blocks), recommend qualified structures for legacy records, explain legacy sections, and generate modern free-format equivalents from descriptions. This assistance speeds incremental modernization without requiring complete rewrites.
Modern RPG combines free-format clarity, ILE modularity, data-centric techniques, and readable style to produce code that is easier to understand, test, extend, and hand off. These characteristics lower maintenance costs, reduce production incidents, shorten enhancement cycles, and support multi-channel strategies while protecting decades of business logic.
Key topics covered:
- Defining characteristics of modern RPG organized into clear categories with practical, shop-focused explanations
- Significantly expanded ILE modularity subsection with bulleted lists for benefits and subprocedure advantages to improve readability
- Emphasis on incremental modernization as an achievable, step-by-step process
Connections for future sections:
- Free-format foundation → expanded in Why Use Fully Free Format RPG, How do I write fully free format RPG?, and How do I convert to free format?.
- ILE modularity, service programs, and procedure benefits → detailed in What is ILE modularization? and How do I modularize RPG programs?.
- Data-centric practices and built-ins → built upon in How do I use embedded SQL in RPG programs?, How do I use modern RPG data structures and arrays, and How do I process JSONs within RPGLE programs?.
What is the business value of modern RPG?
Modern RPG brings direct, practical advantages to IBM i shops that affect budgets, project delivery, operational stability, and the ability to support new business requirements. These benefits emerge from the combination of free-format syntax, ILE-based modularity, structured programming techniques, embedded SQL, and modern data handling features such as DATA-INTO and DATA-GEN. The section explains why these changes matter in real-world IBM i environments and how they translate into measurable outcomes for IT teams and the businesses they support.
The following topics are described in this section:
- Reduced ongoing maintenance costs
- Faster development cycles and shorter time-to-market
- Improved system reliability and lower production risk
- Easier recruitment, onboarding, and retention of developers
- Greater flexibility to support multiple user interfaces and channels
- Protection of long-term investment through incremental modernization
- Lower total cost of ownership for core business applications
Reduced ongoing maintenance costs
Many IBM i shops spend 60–80% of their programming effort fixing, enhancing, or simply understanding existing RPG applications. Legacy code—often written in fixed format, using numbered indicators, monolithic procedures, and duplicated business rules—creates this burden. Every change requires extensive searching to locate every instance of a rule, followed by careful testing to avoid breaking unrelated logic.
Modern RPG addresses the root causes:
- Service programs centralize business rules (pricing, discounts, credit checks, tax calculations) so a single update applies everywhere the rule is used.
- Free-format logic with qualified data structure names (Cust.OrderTotal instead of ORDTOT) and meaningful procedure names makes intent obvious at a glance.
- Removing dead code and lowering cyclomatic complexity (see What is cyclomatic complexity?) reduces the surface area that must be reviewed during changes.
- Shops that refactor high-impact modules into this style commonly report 30–50% reduction in maintenance effort within 12–18 months.
The savings compound over time. A rule change that once took two weeks of analysis, coding, and regression testing now takes a day or two because the logic lives in one well-documented place.
Faster development cycles and shorter time-to-market
Business requirements change constantly—new sales channels, regulatory updates, partner integrations, or competitive features. Legacy RPG slows response because developers spend more time deciphering code than writing new functionality.
Free-format RPG changes that dynamic:
- Syntax resembles modern languages (Java, C#, Python), so developers familiar with those environments adapt quickly.
- Fully free-format RPG (available since IBM i 7.3, fully supported in 7.4 and later) eliminates column constraints, making complex expressions, nested IFs, and long procedure names natural to write.
- Subprocedures with clear parameters and return values replace tangled subroutines, so new features build on stable, testable building blocks.
- Tools such as Rational Developer for i, Git-based source control, and AI assistants like IBM Bob (introduced in IBM i 7.5 and enhanced in 7.6) accelerate editing, refactoring, and debugging.
Teams adopting these practices report 40–60% faster completion of enhancements and new functionality. Release cycles that once spanned months shrink to weeks, letting the business respond to opportunities before competitors do.
Improved system reliability and lower production risk
Production incidents in legacy systems often trace back to side effects introduced during maintenance. Long procedures with many branches, global variables, and indicator-based control flow make it difficult to predict behavior after a change.
Modern RPG reduces these risks in several ways:
- Subprocedures encapsulate logic with explicit interfaces, limiting scope and side effects.
- Unit testing becomes practical when code is modular and dependencies are clear.
- Embedded SQL replaces record-at-a-time loops with set-based operations that are easier to verify for correctness and often perform better.
- Dead code removal eliminates paths that no longer execute but still complicate understanding and testing (see What is dead code and why remove it?).
- Structured error handling with MONITOR blocks and procedure-level error parameters replaces reliance on *PSSR or global indicators.
The outcome is fewer defects reaching production, shorter recovery times when issues do occur, and greater confidence when deploying changes to mission-critical applications.
Easier recruitment, onboarding, and retention of developers
The pool of developers experienced with fixed-format RPG and traditional techniques continues to shrink. Many candidates view legacy RPG as outdated and prefer environments that use contemporary practices.
Modern RPG bridges that gap:
- Free-format code looks and reads more like other enterprise languages.
- Modular ILE structures (service programs, subprocedures) align with object-oriented and service-oriented thinking taught in modern curricula.
- Developers ramp up in weeks instead of months because they recognize familiar patterns: functions with parameters, qualified names, inline expressions.
- Existing staff experience less frustration and burnout when maintaining readable, well-organized code.
This shift reduces the risk of key-person dependency and lowers the hidden cost of relying on a small number of long-tenured RPG specialists.
Greater flexibility to support multiple user interfaces and channels
Businesses increasingly need their core applications to serve green-screen users, web portals, mobile apps, APIs, and partner systems.
Modern RPG enables this without duplicating logic:
- Service programs act as a single source of truth for business rules.
- The same procedures can be called from interactive 5250 programs, REST web services, or even external systems via integrated web services.
- JSON support through DATA-INTO and DATA-GEN simplifies integration with modern front ends and cloud services.
- Open Access handlers allow existing display-file logic to drive browser or mobile interfaces with minimal change.
This reuse protects investment in proven business logic while opening new revenue streams and customer touchpoints.
Protection of long-term investment through incremental modernization
Few shops can afford to rewrite entire applications. Modern RPG supports a low-risk, incremental path:
- Convert logic to free format one program or module at a time.
- Extract reusable rules into service programs without disrupting existing callers.
- Gradually replace legacy file I/O with embedded SQL where performance or clarity improves.
- Use IBM Bob (available in 2026 on IBM i 7.6) for real-time suggestions that replace MOVE with EVAL, indicators with structured logic, or fixed-format declarations with modern equivalents.
This approach delivers value early while steadily moving the codebase toward full modernity.
Lower total cost of ownership for core business applications
When these benefits combine, the overall picture is compelling:
- Maintenance effort drops significantly.
- New features reach production faster.
- Production incidents decrease.
- Talent becomes easier to attract and retain.
- The same logic supports multiple channels without duplication.
- The platform’s native strengths—security, reliability, scalability—remain intact.
For IBM i shops in manufacturing, distribution, finance, or healthcare, modern RPG turns a potential liability (aging code) into a strategic asset. It preserves decades of domain knowledge encoded in RPG while aligning development practices with current expectations.
The result is applications that are less expensive to own, faster to evolve, and ready to support whatever business direction comes next.
For practical guidance on beginning this transition in your own shop, see How do I transition to modern RPG?.
How do I transition to modern RPG?
Transitioning to modern RPG is a practical, step-by-step process that lets any IBM i shop improve code quality, reduce maintenance effort, and increase flexibility without halting business operations or requiring a complete rewrite. The previous section explained the business value: lower long-term costs, faster response to new requirements, easier onboarding of developers familiar with contemporary languages, and stronger support for modern integration points. This section provides a realistic roadmap for making that transition in controlled increments so that benefits appear early and risks stay low.
The guiding principle is to apply modern practices wherever code is already being changed—whether for bug fixes, new features, or regulatory updates—and to use those same techniques on all new development. Over multiple maintenance cycles, the entire application gradually adopts free-format syntax, modular structure, and cleaner logic. This approach makes modernization achievable even in shops with limited resources or large legacy codebases.
The following topics are described in this section:
- Taking stock of the current codebase
- Starting with free-format calculations
- Shifting from subroutines to subprocedures
- Moving shared logic into service programs
- Replacing legacy patterns as opportunities arise
- Incorporating supporting practices and tools
Taking stock of the current codebase
Begin with a quick, honest assessment of the existing RPG sources. Examine:
- Which programs still use older source member types such as RPG, RPG36, or RPG38
- The extent of numbered indicator usage
- How much business logic is duplicated across programs
- The ratio of subroutines to subprocedures
- Reliance on the traditional RPG cycle
Focus first on programs that change frequently or contain core business rules. These deliver the fastest return on effort. Less frequently touched utilities or archival reports can wait. This inventory helps set realistic priorities without trying to boil the ocean.
Starting with free-format calculations
The single most accessible improvement is moving calculation logic into free format. Free-format calculations have been supported for many years and immediately improve readability while reducing common syntax mistakes.
On programs that need maintenance, convert the calculation specifications first, the **FREE directive on releases that support fully free-format sources. Test the change thoroughly in the normal development cycle, then promote it.
The free-format version eliminates rigid column positions, allows longer, more descriptive names, and supports natural operators and expressions. Within a few maintenance cycles, most active programs shift to free-format logic. New programs should use free format from the start.
Shifting from subroutines to subprocedures
Subroutines were effective for many years, but subprocedures provide better organization, explicit parameter interfaces, and proper variable scoping. Begin by converting one well-defined subroutine at a time—especially those that perform a single task such as calculating a total or validating a field.
The conversion makes dependencies clear through parameters instead of relying on global variables. After each conversion, compile, test, and deploy as part of the normal change process. Over time, the program becomes more modular and easier to understand without a disruptive rewrite.
Moving shared logic into service programs
The greatest long-term benefit comes from eliminating duplicated code. Identify business rules that appear in multiple places—tax calculations, credit checks, pricing adjustments, date formatting—and extract them into service programs.
Group related procedures into a module, compile it, and bind it into a service program object. Programs that need the logic bind to the service program rather than carrying their own copy. A single change to the service program immediately corrects or enhances behavior across the entire application.
Start with one or two high-value functions that are used widely and are likely to change over time. As confidence grows, expand the service program with additional procedures. This practice is one of the strongest drivers of reduced maintenance cost and improved consistency.
Replacing legacy patterns as opportunities arise
Whenever a program is opened for enhancement or correction, replace outdated elements with modern equivalents:
- Numbered indicators with named indicators or structured control blocks
- MOVE and MOVEL operations with EVAL, %CHAR, %EDITC, or other built-ins
- CALL statements with CALLP for bound procedures
- Cycle-based logic with linear main procedures
These replacements are low-risk when made in small, tested increments. Tools such as IBM Bob, available in 2026 with IBM i 7.6, can review code in real time and suggest these modern replacements directly in the editor. For example, Bob can propose converting a fixed-format MOVE to an EVAL or replacing indicator logic with an ON-EXCEPT block, significantly reducing the manual effort required.
Incorporating supporting practices and tools
Modern RPG development benefits from editors that understand free-format syntax and ILE binding structures. Source control systems track changes reliably. Unit testing frameworks verify behavior after refactoring. These supporting practices become routine as the team works with free-format and modular code.
The transition does not demand perfection from the beginning. Each maintenance window applies one or more of these techniques. Over months or years, the application moves steadily from legacy patterns to modern RPG practices. The result is code that is easier to read, test, extend, and integrate with new technologies.
For more information on the modular design principles that support subprocedures and service programs, see What is ILE modularization?.
What is ILE modularization?
ILE modularization changes how RPG applications are structured and maintained. Traditional RPG programs are often large, standalone units with duplicated logic, global variables, and subroutines that make changes risky and time-consuming. The Integrated Language Environment (ILE) lets developers break applications into smaller, reusable pieces—modules and service programs—that encapsulate specific responsibilities. This approach reduces duplication, isolates changes to a single place, and allows the same business logic to serve multiple interfaces without modification.
In a working IBM i shop, modularization delivers measurable benefits. A pricing rule or tax calculation lives in one service program; update it once, and every calling program benefits without recompilation (as long as the interface stays compatible). Service programs load once and stay resident, lowering overhead. Binding catches interface errors at creation time. Activation groups control resource scope. These features make code easier to test, debug, and extend—critical when the same rules support 5250 applications, web services, APIs, or mobile front ends.
The following topics are described in this section:
- Core ILE concepts: modules, programs, and service programs
- Subprocedures as the foundation of modular code
- Building and exporting procedures in service programs
- Binding and activation groups in modular designs
- The program entry procedure requirement
- Practical organization patterns for modular applications
Core ILE concepts: modules, programs, and service programs
An ILE module is the unit produced by the CRTRPGMOD command. Modules used should be cycle-less using the NOMAIN keyword in the control specification. NOMAIN modules are ideal for reusable components because they avoid unnecessary cycle overhead.
Modules alone are not executable. Bind them into a program object (*PGM) with CRTPGM or into a service program (*SRVPGM) with CRTSRVPGM. A program object has a single entry point and runs as a standalone unit. A service program functions as a shared library: it exports procedures that bound programs call, with the code loaded once and shared across callers.
Service programs provide the primary reuse benefit. Change exported logic once, and all bound programs see the update immediately, provided the procedure prototype remains unchanged. This eliminates the need for copy/paste or /COPY members that require recompiling every consumer when rules change.
Subprocedures as the foundation of modular code
Subprocedures replace subroutines in modular RPG. Subroutines rely on global variables and cannot be exported cleanly. Subprocedures offer local scope, typed parameters, return values, and export capability.
A subprocedure starts with DCL-PROC and ends with END-PROC. The procedure interface (DCL-PI) defines parameters and return type. Add EXPORT to make the subprocedure reusable from other modules.
Here is an exported subprocedure that calculates age in years:
This subprocedure is self-contained and reusable. A caller invokes it simply:
Subprocedures promote small, focused units that are straightforward to understand and test.
Building and exporting procedures in service programs
Service programs collect related subprocedures into a reusable library. They let you package business logic once and call it from multiple programs, green-screen or otherwise.
Follow these steps to create one:
- Write the subprocedures in a source member, marking those for external use with EXPORT.
Here is a basic example of a reusable utility procedure in fully free-format RPG:
This procedure formats a 10-digit phone number with parentheses and a hyphen. NOMAIN keeps it cycle-less and lightweight for reuse.
-
Compile the source to a module object:
CRTRPGMOD MODULE(REDBOOKCDE/PHONEUTILS) SRCFILE(REDBOOKCDE/QRPGLESRC) DBGVIEW(*SOURCE) -
Define the export list in binder source (strongly preferred over EXPORT(*ALL) for signature stability):
STRPGMEXP PGMLVL(*CURRENT) EXPORT SYMBOL("FormatPhone") ENDPGMEXP This controls exactly which procedures are visible to callers and helps manage version compatibility.
-
Create the service program:
CRTSRVPGM SRVPGM(REDBOOKCDE/PHONEUTILS) MODULE(REDBOOKCDE/PHONEUTILS) EXPORT(*ALL) ACTGRP(*CALLER)
A caller program bound to this service program accesses the procedure naturally:
Use binding directories to manage dependencies cleanly. :
Create the Binding Directory
Add the service program once
Then reference the directory when building programs:
This approach centralizes dependencies and keeps compile commands short.
Binding and activation groups in modular designs
Binding resolves procedure calls at creation time using prototypes and binding directories. Use DFTACTGRP(*NO) for modern ILE programs to avoid the default activation group.
Activation groups control runtime isolation:
- ACTGRP(*CALLER) — Service program runs in the caller's activation group; simplest and most common choice.
- Named activation group (e.g., ACTGRP(REDBOOKCDE)) — Enables persistence across calls; useful for caching.
- ACTGRP(*NEW) — Creates a fresh group per call for strict isolation, but with higher overhead.
Select *CALLER for most service programs unless persistence or separation is required. Activation groups also govern commitment control and open data path behavior in modular database access.
The program entry procedure requirement
Every *PGM object requires a program entry procedure (PEP)—the starting point for execution. Cycle-main modules (compiled without NOMAIN) provide a PEP automatically through the RPG cycle. NOMAIN modules (containing only subprocedures) do not provide a PEP by default.
Attempting to bind only NOMAIN modules into a *PGM triggers CPD5D1A: "No program entry procedure was found." Service programs do not need a PEP—they exist as libraries.
To create a program:
- Include at least one module with a main procedure (cycle-main or MAIN(proc-name)).
- Specify ENTMOD(*FIRST) on CRTPGM if needed to designate the entry module.
Practical organization patterns for modular applications
Organize modular RPG effectively:
- Group procedures by domain: utility service programs (DATEUTILS, STRINGUTILS) and business service programs (ORDERPROC, CUSTRULES).
- Use NOMAIN on all service program modules.
- Store prototypes in /COPY members for consistent interfaces.
- Avoid global variables; pass data explicitly through parameters.
- Limit procedures to a single responsibility (ideally one screen or less).
- Document exported procedures in prototypes.
- Test subprocedures independently (consider RPGUnit).
These patterns make large applications comprehensible. Developers locate logic quickly in well-named service programs instead of monolithic code.
ILE modularization makes RPG a flexible, component-based language that preserves existing investments while supporting modern needs. For transitioning legacy code to this structure, see How do I transition to modern RPG?. For syntax that enables cleaner subprocedures, see Why Use Fully Free Format RPG.
What are RPG program design best practices?
Any good program has a solid design behind it. Without a solid program design, you could be caught by scope creep that interferes with your program completion, implement bugs or have an incomplete project. There are several good practices you can put in place. Before we can talk about best practices, we need to first understand high level vs low level design.
High level design vs low level design
High Level Design (HLD) is the "what and why" the big picture architecture and approach without getting into the details of implementation. For example, the beginning statement would look like this: "We will modernize the order entry system by converting RPG OPM programs to ILE, replacing flat files with DB2 SQL tables, and exposing business logic as service programs consumable by a web front end."
It covers things like the inital business request, system architecture, component relationships, technology choices, data flow between systems, and integration points. A business analyst or project manager can read it and understand the scope. High level designs usually also includes an estimate and possible costs for a project. Most of the time this is to get signoff from the business to proceed with the request.
Low Level Design (LLD) is the "how" the detailed technical specification that a developer actually builds from. For example, you would include the overarching request from the high level design: "We will modernize the order entry system by converting RPG OPM programs to ILE, replacing flat files with DB2 SQL tables, and exposing business logic as service programs consumable by a web front end." This is to ensure that the developer understands the business request. You then will move on to the actual changes.
"The ORDENTRY program will be refactored into three service programs: ORDSV01 (validation), ORDSV02 (pricing), ORDSV03 (fulfillment). The existing ORDHDR and ORDDTL physical files will be replaced with SQL tables with the following columns, keys, and constraints... The API will accept JSON via HTTP POST to endpoint /orders using the YAJL library for parsing..."
It covers specific program names, data structures, field definitions, procedure signatures, error handling logic, and API contracts. Only a senior developer/developer needs to read it.
Ideally you want to have the low level and high level into two separate documents. This ensures that if you have to get sign off that the person reviewing it is not bogged down with low level design details that they dont care about.
Document the business requirements first
Any new program or modification comes with a business request. You must first document and verify what the business is asking you for. When a request comes in ask questions, make sure you and the person requesting the change understands what is being asked of you. Keep a high level design document for that change for you to keep track of what is being asked of you. This does not mean create a new document for every single change that comes in but, that you create the design document for any significant change to an existing program or process.
Document existing business logic.
A new program or modification could effect existing logic. You need to get a handle of what is being effected by this change and document it first. You will add to the high level document that you created when you were given the business request. You want to look at what are the existing database access, inputs and outputs and logic that could be deprecated by the new logic.
Understand the database
Most requests will have some data element to it. This could be having to modify an existing table, add a new view, add a new table, create new sql procedures or functions etc. You want to make sure you understand the data (if applicable) that is being written and what needs to be adjusted. Before you can create any low level design, get a handle of what the data looks like today and what it needs to look like in the future.
Understand inputs and outputs
The next phase of the design is understanding and identifying what your program(s), procedures, sql etc input and outputs are going to be. You need to understand whether you need to add a parameter to a program, call out to a service program procedure and what it needs, call out to the database and what it needs. You need to understand the flow of how the program(s), procedures, sql etc are going to work and how each call in the callstack is going to work together.
Decide on the architecture.
Not every change is going to be a standalone program. Most often than not, you are binding to a service program to call its methods, calling sql procedures or using functions within embedded sql. You need to decide whether you are creating a new service program(s), standalone programs, sql procedures, integration with open source, the use of open source projects like YAJL or open source utilities that exist to make your programming easier.
Complete the design
At this point, you should understand what is being asked of you and you know what architecture you are using, what your database access, inputs and outputs, existing logic and new logic you need to add is going to be. You need to now put that together in a way that a developer can understand. This would look like how the low level design looks above.
"The ORDENTRY program will be refactored into three service programs: ORDSV01 (validation), ORDSV02 (pricing), ORDSV03 (fulfillment). The existing ORDHDR and ORDDTL physical files will be replaced with SQL tables with the following columns, keys, and constraints... The API will accept JSON via HTTP POST to endpoint /orders using the YAJL library for parsing..."
You can also break it up into phases "Phase 1 refactoring ORDENTRY into three service programs ORDSV01 (validation), ORDSV02 (pricing), ORDSV03 (fulfillment)" "Phase 2 replace existing ORDHDR and ORDDTL tables with sql tables orderHeader and OrderDetail using the same fields as previous tables ensuring the use of long name and existing short names for easy interchanability" "Phase 3 map out the database to the api request and response" "Phase 4 add calls to the tested api request and response to ORDSV01 service program in the getRequest and getResponse method"
You are not limited to how many phases you have but, this helps to not only break the work into more manageable pieces but, adds support for easier integration and unit testing.
What are RPG coding best practices.
Coding best practices are key to any program that you develop. When you implement best practices in your shop, you enable your team to be consistent enough without squashing creativity that makes sure one developer can look at anyone's code developed by any memeber of the team.
Dont hard code.
Hardcoding was a practice done by developers in the past. You will encounter it but, dont ever add to it. Imagine what you will have to do if you inherited a series of programs that checked whether they were on the production system or not. The developer before you decided to add logic that checked for the system name/serial number of the IBM i you are developing on. Now imagine needing to switch systems and that system name/serial number changes. You now have a ton of programs that wont work in production because the system or serial number was changed. You never want to hard code a value, if you need to add logic lets say to check whether you are on the production system or not, create a utility service program that has a procedure that returns a boolean, that will tell you if you are on production or not by doing a lookup to a utility table that has the system name soft coded. You want to softcode as much as possible.
API keys, login information.
Todays RPG programs wont be limited to languages native on IBM i. You will most likely integrate with newer technologies via APIs and they will be secured either by using a secret key, user name and password. You dont ever want to hard code those credentials in your programs. Put them in a database file that has limited access and masking so you cant see the values in plain text.
User profiles, emails.
It was very common for a long time to hard code user profiles and/or email addresses for notifications. Now you have employees who leave and their profiles have to be left for one reason or another, emails that will start bouncing once they are deactivated. Create a email table and soft code any notification email addresses and create a lookup program to do the work.
Business logic.
Business logic like item numbers, customer numbers, warehouses etc. You will see them hard coded in many places. There are many different ways to handle this logic and the conditioning you need to do, instead of logic like
You can create a procedure in a service program that will do the work. The procedure could look like this.
The action of hard coding key business logic that you know will be used in multiple places is a hard habit to break. You want to create service programs and procedures that will keep logic in one place.
The list of things not to hard code is vast but, the gist of it is softcode where you can, avoid hard coding like the plague and you will create less work for yourself and others in the future.
Dont be ambiguous with naming
Having naming conventions that require you to have to stop and think to understand what they mean needs to go away. For some they might think if they figure out IT12 = Item12 that they are like a hacker in a hacker movie but the reality is, you wont remember what IT12 means 3 weeks after your change goes live, let alone what another developer has to look at your code. Be purposeful with your names. If you have an item number variable, make that variable make sense. It could be itemNumber, itmNum, ItemNum etc. You want to be able to understand what the variable is by a quick glance.
Naming is not limited to just variables. Procedures, variables, constants are all up for grabs. You want to be able to look at something and know exactly what its doing. For procedures use getXX for returns, setXX for setting something with no return values, is for booleans, has if is does not make sense. For example getItemNumbers, setItemNumbers, isItemNumberActive, hasItemBalance all tell you exactly what the procedure is doing.
Dont write subroutines
Subroutines are logic that is just in a seperate spot in the program and can be called from multiple places. They use global variables and are hard to scope for that reason, use internal procedures to your program or service program procedures if available and applicable.
Limit global variables
Global variables can be touchy but, you need to be purposeful with the use of them. In main RPG programs, global variables are a big part of process but, that prevents you from limiting their scope. Or worse, put a variable (not template, explained below) in a copy book and its global use now just jumped from one program to many. If you have a new RPG program, scope out the logic to use internal procedures with local variables. For example instead of using dcl-s itemDescription varchar( 50 ); at the beginning of your program, create a procedure that uses local variables.
Use service programs procedures when you can where you can
The rule of thumb is if you need the same logic in more than one place then it needs to go into a service program procedure. The first time you are developing the code and it exists in one place only then you can have it as mainline code or an internal procedure to that program but, as soon as you need that logic, extract the code and put it into a service program procedure.
Make use of error handling
RPG gives you monitor/end-mon with on-excp. It is pretty typical in generalized program to have some error handling in place, its a good practice to make use of monitor blocks to prevent your program from having a hard crash.
Comments should make sense
How many times have you read code that has limited, out of date or meaningless comments or worse no comments at all. Program and procedures should have an explanation; this may be as short as a sentence or as long as a few paragraphs. Explain its interactions with the outside world and/or how it should be used; don’t explain its actions with pseudocode. Use proper English grammar.
Be Consistant
Consistency is key, come up with a internal or team standard that makes sense. How you name your variables, comment your code, indent your code etc. Should all be consistant. Its very easy to indent one block of code two places in and the next three places in for no reason other than you just coded it that way. Find what works for you and what your teams standards and make sure to keep applying it in all your coding.
Use like or template where applicable
Template is a important keyword you can apply to variables. They make the template variable unchangeable but, you are able to define another variable like the template you created.
// template variable dcl-s myItemTemplate varchar( 50 ) template; -> dcl-s item like myItemTemplate;
like is a powerful keyword, it helps you define variables, procedure return values, input values etc like something else. This is helpful when you know you are going to have the same variable in multiple places and instead of setting the length in all places, you can have a template variable and reference that variable in all of the places you need to.
Conclusion
Coding best practices can vary based on your teams standards. If standards dont exist then create some for yourself and keep with them using the best practices that were given here. For some places the standard and best practice is to use SQL only. While most will agree with that standard, there are some places where that cannot be a hard and fast rule. Also note that there are best practices that wont ever change like dont hardcode but best practices that can change like how you create a data structure today might not look the same 10 years from now. Best practices can change like the technology we work with, just make sure to change with it.
How do I measure and reduce complexity?
Measuring and reducing complexity ranks among the most practical actions IBM i shops take to keep RPG code maintainable over the long term. Complex code especially legacy monolithic programs with deep nesting and tangled control flow drives up testing effort, increases defect risk, and slows down enhancements. By applying quantifiable metrics and targeted refactoring, developers identify trouble spots early and systematically improve the codebase. In 2026, with IBM i 7.6 and modern tooling, this process becomes far more efficient and integrated into daily work. This section explains the primary metrics, how they apply to RPG, available measurement tools, and concrete steps to lower complexity while preserving business logic.
Understanding cyclomatic complexity
Cyclomatic complexity, developed by Thomas McCabe in 1976, counts the number of independent execution paths through a unit of code. In RPG, each control structure that creates a branch—IF, SELECT/WHEN, FOR, DOW, DOU, MONITOR, and even certain built-in function outcomes—adds to the path count. The base value starts at 1 for a linear block of code, and each decision point adds one.
A simple subprocedure with one IF and one FOR loop typically shows complexity 3. Nested conditions push the number higher quickly: a procedure with three nested IF statements and a SELECT group can easily reach 10 or more. In legacy RPG programs, especially those written before ILE and free-format became common, complexity often climbs well above 30 in large main procedures, making thorough testing impractical and changes risky.
The practical target for individual procedures sits between 10 and 15. Scores above 20 signal code that is hard to understand at a glance and difficult to test completely. Values over 50 indicate serious maintenance debt. Keeping complexity low improves reliability, speeds up debugging, and makes it easier for new developers—many of whom know Java, Python, or JavaScript—to contribute effectively. High complexity directly correlates with higher defect rates and longer onboarding times in IBM i environments.
Complementary metrics
Cyclomatic complexity provides the core insight into control-flow risk, but other metrics round out the assessment.
Halstead metrics measure program volume and difficulty by counting distinct operators and operands. In RPG, this includes operation codes (EVAL, CALLP, IF), built-in functions (%CHAR, %SCAN), and fields. High Halstead volume often accompanies dense, hard-to-read code.
The Maintainability Index combines cyclomatic complexity, lines of code, and comment density into a single score (0–100, higher is better). Modern RPG code should aim for 70 or above.
RPG-specific indicators include:
- Percentage of fixed-format statements (ideally approaching 0 percent)
- Count of obsolete or legacy constructs (MOVE without %CHAR, CALL without prototypes, numbered indicators)
- In-line declarations (result-field lengths or *LIKE DEFINE scattered throughout logic)
- Heavy use of numbered indicators (01–99), which obscure decision logic and inflate perceived complexity
Tracking these alongside cyclomatic complexity reveals migration progress and highlights areas where readability improvements yield the biggest gains.
Tools for measuring complexity
Several tools help collect these metrics on IBM i.
Static analysis products such as ARCAD Observer, Fresche X-Analysis, and SourceMeter deliver detailed reports on cyclomatic complexity, Halstead values, and RPG-specific issues across entire applications. Open-source options, including older RPG Code Metrics analyzers, provide basic counts for decision density, fixed-format usage, and obsolete opcodes.
Dynamic tools like iTrace complement static analysis by showing which paths actually execute during real workloads, helping prioritize refactoring on frequently run complex sections.
IBM Bob, integrated into VS Code for IBM i 7.6, brings real-time intelligence directly into the editing experience. Bob analyzes RPG code on the fly and specifically addresses cyclomatic complexity through these features:
- Performs automated structural and complexity analysis, calculating McCabe cyclomatic complexity (and Halstead metrics) for procedures and modules
- Flags high-complexity sections in real time within the editor, highlighting procedures or changed code that exceed safe thresholds
- Identifies low maintainability linked to elevated complexity scores during code reviews
- Offers contextual refactoring suggestions to reduce complexity, such as extracting logic into new subprocedures, flattening nested conditions, or replacing legacy control structures with modern equivalents, complete with explanations of the expected impact on testability
- Aggregates complexity metrics across source members or projects to guide modernization prioritization
- Reviews uncommitted changes immediately, integrating complexity checks into the daily workflow
- Supports modernization-focused modes that pair complexity metrics with recommendations for moving from fixed-format to free-format RPG and adopting ILE modularization
These capabilities make IBM Bob particularly effective for gradual, low-risk modernization of existing codebases.
Practical refactoring techniques
The most reliable way to reduce complexity involves decomposing large units into smaller, single-responsibility procedures.
Begin with extraction: move a block of complex logic—validation, calculation, or error handling—into its own subprocedure. A 400-line monolithic procedure with multiple nested IFs and loops often splits into 8–12 smaller units, each dropping below complexity 10. This preserves behavior while shrinking path counts dramatically.
Fully embrace ILE modularity. Place reusable logic in service programs bound by reference, eliminating duplicated decision structures across programs. Prototyped interfaces replace older CALL operations, reducing parameter-related complexity.
Convert to fully free-format RPG wherever possible. Free-format syntax encourages cleaner expressions (using %EQUAL instead of indicator checks, %XLATE instead of loops) and naturally lowers both actual and perceived complexity.
Refactor control flow aggressively:
- Replace deep nesting with early returns or consolidated SELECT/WHEN groups
- Eliminate unnecessary loops by shifting logic to SQL set-based operations (covered in Chapter 8)
- Use modern built-ins to simplify string and date handling
After each change, run regression tests—preferably automated with RPGUnit—to confirm correctness. Monitor metrics before and after to quantify progress.
Building and using a complexity dashboard
Create a simple dashboard in a spreadsheet, database file, or reporting tool. Capture per-module metrics: cyclomatic complexity, lines of code, maintainability index, free-format percentage, and obsolete construct count. Update after each sprint or modernization wave.
Review the dashboard in team stand-ups or planning sessions. Establish rules such as no new code above complexity 15 and targets for reducing legacy modules by 20 percent per quarter. The data highlights training gaps (for example, overuse of nesting) and demonstrates concrete value—lower defects, faster enhancements, easier handoffs.
Systematic measurement and reduction of complexity, supported by tools like IBM Bob, turns a daunting legacy codebase into one that is responsive and future-ready. These practices build on the modular foundation described in What is ILE modularization? and set the stage for dead code elimination (What is dead code and why remove it?).
What is cyclomatic complexity?
Cyclomatic complexity provides an objective measure of how complicated a program's control flow really is. Introduced by Thomas J. McCabe in 1976, this metric counts the number of linearly independent paths through a section of code—typically a procedure or module. In RPG terms, it tells you how many different ways the logic can execute based on decisions, loops, and conditions. Lower values indicate simpler, more predictable code that is easier to understand, test, and maintain. Higher values signal potential trouble spots where bugs hide and future changes become risky.
In a modern IBM i shop, cyclomatic complexity matters because legacy RPG programs often accumulate nested IFs, multiple WHEN groups in SELECT, DO loops with embedded conditions, and scattered branching logic. These structures multiply execution paths quickly. A single procedure with a complexity of 30 or more can require dozens of test cases to achieve reasonable coverage, and even then, subtle defects slip through. When maintenance programmers encounter high-complexity code, they hesitate to make changes, leading to workarounds, duplicated logic, or patches that increase technical debt further.
The practical value of tracking cyclomatic complexity lies in guiding refactoring decisions. During modernization efforts, teams use this metric to prioritize which procedures or service programs to break apart first. A high score flags code that should be split into smaller, focused subprocedures—each responsible for a single decision branch or loop. This refactoring reduces risk, improves unit testability, and makes the overall application more responsive to business changes. Tools that compute the metric automatically help enforce team standards: many organizations set a target of 10 or less per procedure, with anything above 20 requiring review before promotion.
How cyclomatic complexity is calculated
The formal definition uses a program's control flow graph, where nodes represent blocks of sequential code and edges represent transfers of control (jumps caused by conditions, loops, or calls). The McCabe formula is:
Cyclomatic complexity (CC) = E - N + 2P
- E = number of edges (control transfers)
- N = number of nodes (sequential code blocks)
- P = number of connected components (usually 1 for a single procedure)
For most RPG subprocedures, which have a single entry and exit, P = 1, so the formula simplifies to E - N + 2. In practice, developers rarely draw graphs. Instead, they count decision points (predicate nodes) and add 1:
CC ≈ number of decision points + 1
In RPG IV and later, each of the following adds one to the count:
- Each IF statement (regardless of nesting level)
- Each ELSEIF (or WHEN in a SELECT group)
- Each DO / DOW / DOU / FOR loop
- Each AND / OR in a complex condition (some analyzers count these separately)
- CASxx operations in older code
- MONITOR / ON-ERROR blocks (each ON-ERROR adds a path)
A simple procedure with no decisions has CC = 1. Adding one IF increases it to 2 (true and false paths). Nesting another IF inside raises it to 3, and so on.
Consider a basic validation routine:
This procedure has three decision points: two in the first IF-ELSEIF chain (treated as two branches) and one more IF for the total price check. Cyclomatic complexity is therefore 4 (3 decisions + 1 base path). Testing needs to cover four scenarios: valid order, zero quantity, insufficient stock, and high total price.
Now examine a more complex version with nested logic:
Here, the outer IF-ELSEIF creates two main branches, but each contains nested IFs. Counting decisions: outer IF (1), inner IF for CC (2), another for limit (3), authorize (4); elseif branch has two more IFs (5,6). Total decisions ≈ 6–8 depending on exact analyzer. Complexity easily reaches 7–9, meaning 7–9 independent paths. This single procedure now demands extensive testing, and a small change (such as adding a fraud check) risks breaking unrelated paths.
Interpreting complexity scores
McCabe's original guidance suggested:
- 1–10: simple, low risk
- 11–20: moderate complexity, manageable with care
- 21–50: high complexity, refactor recommended
- Over 50: very high, serious maintenance risk
In RPG modernization projects, teams often adopt stricter targets. A service program procedure with CC > 15 typically gets flagged for splitting. The Software Engineering Institute and many quality standards recommend keeping individual modules under 10–15 to ensure reliable testing and low defect rates.
High complexity correlates strongly with defect density. Studies show that procedures above 20 have significantly higher bug rates because paths interact in unexpected ways. In IBM i environments, where many programs run unattended for years, hidden defects in high-complexity code can cause costly production failures when data patterns change.
Using cyclomatic complexity in practice
During code reviews, calculate or display CC for each subprocedure. Many modern tools integrated with Rational Developer for i, VS Code extensions, or open-source analyzers provide this automatically. When a score exceeds the threshold, refactor by:
- Extracting nested logic into separate subprocedures
- Replacing deep nesting with early returns or guard clauses
- Converting complex SELECT/WHEN groups to lookup tables or polymorphic calls
- Simplifying conditions with helper procedures
As discussed in How do I measure and reduce complexity?, tracking CC over time shows progress in a modernization effort. A downward trend in average procedure complexity indicates cleaner, more maintainable code.
Cyclomatic complexity is not perfect—it ignores data complexity and external calls—but it provides a reliable, language-independent starting point for identifying trouble spots. In modern RPG, keeping procedures simple enough that their complexity stays low makes unit testing practical, onboarding faster, and long-term maintenance far less painful. Every shop should incorporate this metric into its quality gates to prevent legacy-style spaghetti logic from creeping back in.
What is dead code and why remove it?
Dead code consists of program statements, blocks, subroutines, or procedures that no longer execute or affect the program's behavior in any production scenario. In IBM i RPG applications, these unused fragments accumulate over years of maintenance and represent a form of technical debt that silently increases maintenance costs and modernization effort.
This section defines dead code in the RPG context, explains how it typically appears in legacy and modernizing codebases, and outlines the practical reasons to identify and eliminate it. Removing dead code improves readability, reduces risk during changes, and prepares the program for refactoring, modularization, and free-format conversion.
The following topics are described in this section:
- Common forms of dead code in RPG programs
- How dead code accumulates in IBM i applications
- Risks of keeping dead code in the source
- Benefits of systematic removal
Common forms of dead code in RPG programs
Dead code takes several recognizable forms in RPG source members:
- Unreachable logic after a condition that never evaluates true in practice (for example, an
IF branch based on a discontinued business rule) - Subroutines that no longer receive control because calling logic now uses procedures or different flows
- Calculations or data moves whose results are never referenced (computed but unused)
- Commented-out blocks left from previous versions or debugging sessions
- Entire procedures or copybook sections included but never called in the current application
- Legacy error-handling paths for conditions that modern database constraints or input validation prevent from occurring
In fixed-format RPG III and RPG/400 programs, dead code often hides in indicator-driven logic or in subroutines chained through GOTO or EXSR statements that modern control structures have replaced.
How dead code accumulates in IBM i applications
Several common practices lead to dead code buildup in long-lived applications:
- Business processes evolve or are replaced, but old conditional paths remain “just in case”
- Time pressure leads developers to copy-paste code from older programs, introducing unused sections
- Temporary patches or workarounds are added without removing the original logic after the fix is permanent
- Multiple programmers maintain the same source over decades, each adding layers without full visibility into prior code
- Feature additions or bug fixes create alternative paths, leaving the original path intact but dormant
- Commented code is retained for historical reference or fear of breaking something undocumented
These patterns are especially common in monolithic programs that handle multiple related functions in a single source member.
Risks of retaining dead code
Leaving dead code in place creates several ongoing problems:
- Inflates perceived program complexity, making the active logic harder to understand at a glance
- Increases cognitive load for developers reading or modifying the code
- Distorts static analysis metrics such as line count and cyclomatic complexity
- Raises the chance of reactivating dormant code during refactoring, introducing defects
- Slows compilation, source navigation, and code reviews
- Obscures the true business intent of the program
- Complicates onboarding of new developers unfamiliar with legacy RPG patterns
- Wastes memory and activation resources (minor but measurable in high-volume environments)
In modernization projects, dead code inflates the scope of work: tools, conversions, and refactoring efforts process unnecessary logic.
Benefits of removing dead code
Eliminating confirmed dead code delivers clear, practical advantages:
- Sharper focus on active business logic, improving readability and maintainability
- Reduced time spent understanding and navigating source during debugging and enhancements
- Lower risk when making changes, as developers no longer second-guess irrelevant sections
- More accurate test coverage metrics and complexity measurements
- Faster compile times and quicker loading in modern IDEs such as Rational Developer for i
- Smaller, cleaner codebases that are easier to hand off or modularize into service programs
- Significant reduction in modernization effort—many legacy programs contain 20–40% dead or obsolete code
- Alignment with modern RPG principles of clarity, self-documentation, and minimalism
Teams that routinely remove dead code report fewer defects during refactoring, faster feature delivery, and easier adoption of free-format RPG and ILE practices.
Cautions when removing dead code
Removal requires careful validation because much dead code is runtime-dependent rather than statically unreachable. Relying solely on static analysis risks deleting logic that executes only under rare conditions. Dynamic analysis—executing the program in a representative environment with comprehensive test scenarios—is essential for confidence.
The safest approach follows these steps:
- Identify candidates through tracing or coverage tools
- Comment out suspect sections first and monitor for behavioral changes
- Perform regression testing with business users validating key scenarios
- Delete only after no issues surface over time
This disciplined process minimizes risk while steadily cleaning the codebase.
In modern RPG, dead code stands in direct opposition to the goals of maintainability and future-readiness. By systematically identifying and removing it, IBM i developers create code that is easier to understand, test, and extend. This cleanup step pays dividends throughout the modernization journey, especially when moving toward What is ILE modularization? or reducing complexity (see How do I measure and reduce complexity? and How do I modularize RPG programs?). Modern AI tools like IBM Bob can easily help identify and remove dead code.
How do I modularize RPG programs?
Modularizing RPG programs means breaking large, tightly coupled monolithic applications into smaller, independent, reusable components that communicate through well-defined interfaces. This approach, made practical by the Integrated Language Environment (ILE), reduces maintenance effort, minimizes defects when changes occur, eliminates redundant code across applications, and makes unit testing far more feasible. In a typical IBM i shop, modularization turns a single 5,000-line order-entry program into a main driver that calls shared business logic housed in service programs—logic that the same shop can then reuse for web APIs, mobile back-ends, or batch reporting without duplication.
The following topics are described in this section:
- Moving from subroutines to subprocedures
- Organizing logic into modules
- Packaging reusable procedures in service programs
- Creating a module for the main application logic and binding it to service programs
- Accelerating the process with IBM Bob
Move from subroutines to subprocedures
Legacy RPG programs organize internal reusable code with subroutines (BEGSR/ENDSR). Subroutines operate in the global scope of the program, share all variables, and rely on indicators or global fields to pass information. This design creates hidden dependencies: changing a variable in one subroutine can break unrelated logic elsewhere. Subprocedures solve these problems by providing local scope, typed parameters, return values, and explicit interfaces.
Begin the transition by identifying a self-contained subroutine. Determine what data it needs as input and what it must produce as output. Move those values into parameters and return the result instead of modifying globals.
Consider a typical subroutine that validates an order total against credit limits:
This code depends on global fields ORDAMT, CRTLIM, and CREDIT_ERR. Any other part of the program can accidentally change these fields before or after the subroutine runs.
Replace it with a subprocedure:
Call the subprocedure like this:
The subprocedure is now self-contained. It cannot be affected by unrelated code, and its behavior is predictable based only on the parameters passed. This change alone makes debugging easier and enables isolated testing of the credit-check logic.
Organize logic into modules
A module (*MODULE) is a compiled object that contains one or more subprocedures. When created with the NOMAIN keyword, the module has no main procedure and exists solely to provide reusable code.
Group related subprocedures into a single module based on responsibility. For example, create a module named CUSTUTIL for all customer-related operations:
Compile the source to a module:
Modules become the basic unit of reuse. Keep each module focused: one module for customer functions, another for order calculations, another for date utilities. Cohesive modules with 5–20 subprocedures strike a good balance between manageability and reduced overhead.
Package reusable procedures in service programs
Service programs (*SRVPGM) collect one or more modules into a single object that multiple applications can bind to and share at runtime. This is the key to eliminating copy-paste duplication of business rules across green-screen, web, and API programs.
Create the service program from the module using the simplest form (which exports all procedures by default):
This command produces a service program that exports every exported subprocedure from the module. For introductory modularization, this approach avoids extra complexity while still delivering the core benefit: shared, callable business logic.
Creating a module for the main application logic and binding it to service programs
In a fully modular ILE application, even the entry-point logic belongs in a module. The ORDENTRY module contains the main procedure that drives the application and calls subprocedures from service programs. Create ORDENTRY as a module with a main procedure (specified via the MAIN keyword), then bind it to the service program when creating the final *PGM object.
Here is the ORDENTRY module source. The DSPly operation sends an information message to the message queue of the user running the program (typically QSYSOPR or the user's own message queue). You can view these messages by typing DSPMSG at a command line. Prototypes are shown inline for this example; in practice, move them to a separate prototype member.
Compile ORDENTRY to a module:
Create the final program by binding the ORDENTRY module to the CUSTSRV service program:
This ORDENTRY module focuses on application flow while delegating business rules to the service program. The DSPly operation with '*REQUESTER' sends messages to the interactive user's message queue, making them visible with DSPMSG. In production code, replace DSPly with a display file or dedicated message handling for more robust output.
Binding by reference to service programs keeps program objects small and allows the service program to be updated independently (as long as the exported procedures remain compatible). This separation is especially valuable in shops that deliver changes frequently.
Incremental modernization and practical tips
Start modularization on high-value areas: pricing logic, tax calculations, credit checks, or address validation—routines duplicated in multiple programs. Extract one routine at a time into a subprocedure, move it to a module, build the service program, then create and bind the entry-point module.
Adopt qualified data structures inside subprocedures to avoid name collisions and improve readability.
Monitor binding errors during testing to catch interface mismatches early. Use DSPPGMREF and DSPMOD to document dependencies.
Modular RPG code becomes the foundation for cleaner design, easier testing, faster onboarding of new developers, and flexible deployment across interfaces. The effort pays off quickly in reduced defects and faster response to business requests.
Accelerating modularization with IBM Bob
IBM Bob, available since IBM i 7.6, brings AI assistance directly into the VS Code-based IDE and significantly speeds up the modularization process described in this section. Bob understands full RPG programs—including legacy fixed-format, ILE structures, subroutines, data flows, and dependencies—and can analyze large monolithic sources to identify modularization opportunities.
Highlight a subroutine in your editor and ask Bob to extract it as a subprocedure. Bob automatically:
- Identifies input and output fields.
- Converts globals to typed parameters (using CONST where safe).
- Generates the DCL-PROC wrapper, local variables, and return value.
- Produces a matching prototype for /COPY inclusion.
- Suggests meaningful names if the original variables are cryptic.
For broader refactoring, provide a prompt like "Analyze this order-entry program and extract all pricing logic into a new service program named PRICING." Bob maps the relevant subroutines, consolidates duplicated code, generates the module source, and updates the main program to call the new service program via prototyped calls.
Bob also performs impact analysis before changes, showing which programs reference the affected logic, and generates basic unit-test stubs or integrates with RPGUNIT. This reduces manual risk when extracting high-value routines.
In practice, Bob turns weeks of careful manual extraction into hours of assisted work with review steps. Shops report faster progress on modularization projects because Bob handles repetitive pattern recognition and boilerplate while preserving business intent. Use Bob in combination with the incremental approach: start with one duplicated routine, let Bob propose the extraction, review and test, then bind the result into existing applications.
For background on the ILE concepts that enable this approach, see What is ILE modularization?.
Why Use Fully Free Format RPG
For decades, RPG has been a cornerstone of application development on IBM i. However, traditional fixed-format RPG, rooted in its punch-card era constraints, imposes rigid column-based rules that make code difficult to read, write and maintain. With the introduction of fully free format RPG in IBM i 7.1, developers are no longer bound by these limitations. Instead, they can write code in a modern, flexible syntax that closely resembles contemporary programming languages like Java, C#, and Python.
This evolution is not just about syntax, it represents a fundamental shift in how developers can approach coding on IBM i, unlocking new levels of productivity, maintainability, and integration capabilities. By removing the burden of column positioning and enabling clear declarations (
Better readability
Readable code is cheaper to maintain. Most of the life of an IBM i application developer is spent enhancing, debugging, and maintaining existing code rather than writing new code. Fully free format RPG improves readability in several ways:
- It eliminates position-based syntax and most column-related mistakes.
- It allows for meaningful variable names such as
OrderTotal orTransactionStatus , making the code self-explanatory. - It supports natural indentation which enhances the visual structure of the code and makes it easier to follow the flow of logic.
- It enables the use of modern constructs like
if/else ,for loops, andprocedure calls that are familiar to developers from other languages.
Consider the difference:
Fixed Format:
Fully Free Format:
The free format version is immediately recognizable to developers familiar with modern programming languages.
Easier Maintenance and lower risk
Fully free format does not change the business rules in a program, but it makes them easier to understand and modify. When code is clearer, developers can make changes with more confidence. Reviews are faster, debugging is simpler and the chance of introducing new bugs is reduced. This leads to lower maintenance costs and faster turnaround times for enhancements and fixes. This is very important for IBM i applications, which often have long lifecycles and are maintained by multiple developers over time.
Stronger fit with modern development practices
Modern RPG encompasses fully free format syntax combined with advanced language features that align with contemporary programming practices. Key characteristics include:
- ILE modularity: Service programs, exported procedures, and subprocedures are easier to define and organize.
- Built-in Functions: Operations such as
%trim ,%date ,%timestamp and%replace read more naturally in free format. - Qualified Data Structures: Modern declaration are clearer and less error-prone than older position-based definitions.
- Integration Capabilities: Enables seamless integration of SQL, JSON, and web services in free form source.
- IDE integration: Leverages modern compilation and binding techniques withing the ILE environment or IDEs like VS Code, which provide features such as content assist, syntax checking, and debugging for RPG development.
Easier onboarding for new developers
One of the biggest practical benefits of fully free format RPG is that it lowers the entry barrier for new developers. Programmers having knowledge of modern programming languages such as Java, C#, or Python will find the free format syntax more familiar and easier to learn. This can help organizations attract new talent to work on their IBM i applications and reduce the time required for training and onboarding. The modern syntax allows new developers to quickly become productive and contribute to the codebase without struggling with the quirks of fixed-format RPG.
Better tooling and source control integration
Fully free format RPG is better suited for modern development tools and source control systems. The clear syntax and structure of free format code make it easier to use with editors like VS Code and version control systems like Git, enabling better collaboration among developers. It also makes code reviews more efficient. Additionally, modern IDEs can provide enhanced features such as syntax highlighting, code completion, and refactoring tools that work more effectively with free format code. This leads to improved developer productivity and a more efficient development workflow.
Practical modernization path
Another very good reason to use fully free form is that adoption can be gradual. Developers do not need to rewrite their entire fixed-format codebase at once. New development can be done in free format while existing fixed-format code continues to work without modification. Over time, as developers become more comfortable with the new syntax, they can choose to convert existing code to free format using available conversion tools or as part of regular maintenance activities. This allows organizations to modernize their applications incrementally without disrupting ongoing development or maintenance efforts.
IBM continues to enhance fully free format RPG with new features and capabilities. By adopting this format, developers ensure their code remains current and can take advantage of future language improvements. Fixed-format RPG, while still supported, is unlikely to receive major new features.
Conclusion
Fully free format RPG is worth using because it improves readability, lowers maintenance risk, aligns with modern development practices, allows easier knowledge transfer and makes it easier to onboard new developers. It helps developers spend less time wrestling with formatting rules and more time focusing on business logic. The shift to fully free format is not just about syntax, it's about embracing modern development practices that will serve your organization for years to come. Not only shoud free format there are great options for moving from fixed to free tools like IBM Bob can esily help you with this trasistion. The best part, the core function of the code does not change, just the readability to the developer.
How do I write fully free format RPG?
What This Covers
This procedure covers how to write RPGLE programs in fully free format — no fixed columns, no /FREE and /END-FREE delimiters, no H-specs. Just code.
What It Is
IBM added fully free format RPG to the platform years ago, and on 7.5 and 7.6 it's the default way to write new programs. Before fully free, "free format" still required the
Three eras of RPG source layout:
The key difference: in fully free format, the source member type is still RPGLE, but the first line must start in column 1 (not column 6). If the compiler sees anything in columns 1–5 on the first line, it knows it's fully free. If column 6 has a spec letter, it falls back to fixed/mixed parsing.
One gotcha that trips people up: you can't mix fully free and fixed format in the same source member. It's one or the other. You can
Why It Matters
Fixed format RPG is the #1 reason new developers bounce off the platform. Show a Java or Python developer a fixed-format RPG source member and they'll assume the language hasn't changed since 1988. Show them fully free format and they'll see something that looks like a modern procedural language — because it is.
There's a practical side too. Fixed format means you're debugging column alignment problems instead of business logic. A misplaced character in column 7 vs. column 6 turns a valid statement into a compiler error that says nothing useful. Fully free format eliminates that entire category of mistakes.
And if your shop uses Git (you should), fixed-format diffs are painful. Every line looks like it changed because someone's editor reformatted the columns. Fully free format diffs show actual code changes.
How It Works
We'll convert a small fixed-format RPG program to fully free format, piece by piece.
The fixed-format original — a program that reads a customer record by ID and returns the name:
A. Replace H-spec with CTL-OPT
Fixed format:
Fully free:
B. Replace F-spec with DCL-F
Fixed format:
Fully free:
The column-encoded attributes (
C. Replace D-specs with DCL-PR, DCL-PI, DCL-S, DCL-DS
Fixed format:
Fully free:
Data types change from positional notation to keyword notation.
Quick reference for the common type conversions:
D. Replace C-specs with free-form operations
Fixed format:
Fully free:
Drop
E. The complete fully free program
F. Using embedded SQL in fully free format
If your program uses embedded SQL, the
No change in SQL syntax — just the RPG wrapper around it is free format. The
If your shop has moved source out of
Key Takeaways
- Fully free format is the standard for new RPG development — every new program should use it
- You can't mix fixed and fully free in the same source member — pick one;
/COPY members can be a different format **FREE on line 1 is optional on 7.5 and 7.6 — include it anyway for clarity and intent- Drop EVAL — assignments are just
variable = value; - Every statement ends with a semicolon — including
CTL-OPT ,IF ,ELSE ,ENDIF ,RETURN - Start new programs in fully free — converting existing programs is a nice-to-have, not a priority; don't rewrite working code just for the format
- RDi and VS Code (with Code for IBM i) both support fully free — syntax highlighting, content assist, and error checking all work
How do I convert to free format?
Converting fixed-format RPG IV source members to fully free-format RPG removes the decades-old column constraints, produces code that reads more naturally, reduces visual noise, and aligns programs with current IBM i language standards. Starting with IBM i 7.2, fully free-format RPG allows every line—declarations, control options, calculations, and comments—to begin in column 1 and continue without positional restrictions. This style uses clear keywords such as ctl-opt, dcl-f, dcl-s, dcl-ds, dcl-pr, and dcl-pi, along with straightforward assignment and expression syntax. The conversion preserves program behavior while making the source significantly easier to maintain, review, and extend.
For IBM i shops running 7.2 or later, this change delivers immediate practical value. Developers scan logic faster, spot errors more readily, and spend less time fighting syntax quirks. New team members adapt quickly, especially those familiar with contemporary languages. The codebase also becomes fully prepared for modern features such as DATA-INTO, %JSON functions, advanced array handling, and cleaner embedded SQL integration. Many shops find that converting to fully free-format RPG yields one of the highest returns on modernization effort because the change applies broadly with low risk when approached systematically.
The following topics are described in this section:
- Preparing the source member
- Performing automated conversion
- Manual cleanup and modern refactoring
- Dealing with I- and O-specifications
- Testing and verification
- Reaching a high-quality modern state with IBM Bob assistance
Preparing the source member
Confirm the member uses type RPGLE (or SQLRPGLE when embedded SQL is present). If the source still contains any fixed-format RPG IV elements, make a working copy—typically by appending _FREE to the member name—or rely on source control to preserve the original.
No special preparation beyond a reliable backup is required for IBM i 7.2 and later releases, as fully free-format RPG is natively supported without additional PTFs or directives in these versions. The ** FREE directive (case-insensitive) placed in column 1 signals the compiler to use fully free-format mode and is the recommended way to begin modern sources.
Performing automated conversion
Several tools automate the bulk transformation from fixed-format RPG IV to fully free format.
In VS Code with the Code for IBM i extension and appropriate RPG language support, open the member, select the relevant lines or the entire file, and invoke the free-format conversion command (via context menu or command palette). These tools rewrite calculation specifications as natural expressions, convert legacy opcodes to modern equivalents (ADD to +=, compatible MOVE to direct assignment), and restructure conditional and looping logic.
For conversions across large applications or entire libraries, third-party IBM reseller tools such as ARCAD Transformer RPG or Fortra RPG Modernization Suite provide batch processing with consistent, configurable results. These often allow rules for qualifying data structures, renaming indicators, and applying naming standards during the initial pass.
The latest option for converting code to modern free format is IBM Bob, an AI based Vscode centered developmemt partner. Bob does a great job of updating the source from old hard to read Fixed format to a modern RPG based view. Bob can also help with converting iSpec and oSpec to the proper new equilivants.
Automated conversion usually produces compilable code, though manual review remains essential to achieve current best practices.
Manual cleanup and modern refactoring
Start the source with the ** FREE directive in column 1. In IBM i 7.2 and later, this single line enables fully free-format mode across the entire member—no /FREE or /END-FREE directives are needed or used in modern code.
Rewrite declarations using contemporary keywords:
- Control specifications become ctl-opt lines.
- File declarations become dcl-f.
- Standalone fields and constants become dcl-s or dcl-c.
- Data structures become dcl-ds.
- Prototypes and procedure interfaces become dcl-pr and dcl-pi.
Example — legacy standalone field:
Modern equivalent:
Example — legacy qualified data structure:
Modern equivalent:
Refactor calculations to expressive statements. Chain operations where logical, use built-in functions such as %trim, %upper, %char, and %editc, and replace indicator-driven logic with structured IF, SELECT/WHEN, DOW, and DOU blocks.
Replace numbered indicators with named boolean fields:
Dealing with I- and O-specifications
Input specifications (I-specs) and output specifications (O-specs) remain fixed-format; fully free RPG does not provide free-form alternatives. Retain them where necessary, or refactor the program to reduce or eliminate dependence on them.
Shift program-described files to externally described files (DDS or SQL DDL) whenever possible. For output processing, migrate to embedded SQL result sets, DATA-GEN for JSON or CSV, or Open Access handlers for modern presentation layers. Encapsulate any remaining I/O specifications inside small procedures to limit their scope.
Testing and verification
Compile the converted member with CRTBNDRPG or CRTRPGMOD and bind it into the same program or service program structure as before. Execute thorough testing: unit tests for procedures, integration tests for modules, and full regression tests for the application.
Validate key results—database updates, spool files, display file behavior—against the original version. Use iTrace to confirm execution coverage remains consistent. Optionally compare cyclomatic complexity metrics before and after (see What is cyclomatic complexity?); the improved structure of free-format code often lowers perceived complexity.
Reaching a high-quality modern state with IBM Bob assistance
IBM Bob, the AI-assisted development tool integrated into VS Code for IBM i, significantly accelerates conversion and improves final quality. Bob can:
- Convert highlighted fixed-format code to fully free-format syntax on request.
- Suggest modern equivalents for legacy operations (for example, replacing MOVE with edit words using %editc or %char).
- Recommend qualified data structures and named indicators to eliminate numbered indicators.
- Identify subroutines suitable for conversion to subprocedures and generate the necessary skeletons.
- Apply consistent formatting, logical blank-line separation, and descriptive naming conventions.
- Flag potential issues such as type mismatches introduced during opcode replacement.
Developers typically run automated conversion first, then use Bob iteratively to refine declarations, expressions, and structure—often producing cleaner, more maintainable code in substantially less time.
After refinement, adopt these final practices:
- Select clear, meaningful names for variables and procedures.
- Qualify data structures to prevent field name collisions.
- Replace subroutines with subprocedures.
- Eliminate obsolete opcodes and traditional indicators.
- Use consistent indentation and blank lines to separate logical blocks.
Converting to fully free-format RPG in IBM i 7.2 and later represents a foundational modernization step. The daily improvements in readability, maintainability, and developer productivity make the effort worthwhile for any shop maintaining RPG applications.
How do I use embedded SQL in RPG programs?
What This Covers
This procedure covers embedded SQL in RPGLE — host variables, cursors, error handling, dynamic SQL with parameter markers, NULL indicators, and transaction control. If you're writing new RPG and hitting the database, embedded SQL is how you do it.
What It Is
Embedded SQL lets you write SQL statements directly inside your RPGLE source. The SQL precompiler extracts them, builds access plans, and wires the results into RPG host variables. No CALL to a stored procedure, no API — the SQL is inline with your program logic.
Two compilation paths:
On 7.5 and 7.6,
Why It Matters
Native RPG file I/O (
Embedded SQL gives you the full power of Db2 for i inside your RPG program. You get:
- Joins across multiple files in a single statement
GROUP BY ,HAVING ,ORDER BY without building your own sort- Scalar and aggregate functions (
COALESCE ,TRIM ,SUM ,COUNT ) - Access to SQL views, CTEs, and table functions
- Consistent NULL handling instead of RPG's "blank means null... maybe" convention
The performance story is straightforward: the SQL optimizer builds access plans at precompile time and caches them. For complex access patterns, SQL will outperform chained READ loops with runtime filtering.
How It Works
All examples use this customer master table:
A. Host variables — moving data between SQL and RPG
Host variables are RPG variables prefixed with
Rules for host variables:
- Prefix with
: inside SQL, use bare name in RPG - RPG data types map automatically —
CHAR(10) toCHAR(10) ,PACKED(11:2) toDECIMAL(11,2) VARCHAR RPG variables map to SQLVARCHAR — length and data travel togetherSELECT INTO is for single-row results only. If the query might return multiple rows, use a cursor (section C)
B. SQLCODE and SQLSTATE — knowing what happened
Every SQL statement sets two status fields. Check them.
A common mistake: checking only
C. Cursors — processing multiple rows
When a query returns more than one row, you need a cursor. Declare it, open it, fetch in a loop, close it.
The
Scrollable cursors — if you need to move backward or jump to a specific row:
Scrollable cursors cost more — the database builds a temp result set to support random access. Use them only when you genuinely need to navigate, not as a default.
D. INSERT, UPDATE, DELETE — modifying data
DML statements use host variables the same way:
E. NULL handling with indicator variables
NULLs in SQL don't map to any RPG value — there's no concept of "absent" in RPG. Indicator variables bridge the gap.
The indicator variable sits right after the host variable — no comma between them. That syntax trips people up the first time.
You can also use indicators on INSERT to set a column to NULL:
Indicator arrays for data structures — when you're fetching into a DS with multiple fields, you can declare a single indicator array instead of one indicator per field:
F. Dynamic SQL with parameter markers
When you don't know the SQL at compile time — building WHERE clauses based on user input, for instance — use dynamic SQL with parameter markers. Never concatenate user input into SQL strings.
Parameter markers (
EXECUTE IMMEDIATE — for DDL or one-shot statements where you don't need a result set:
Don't use EXECUTE IMMEDIATE for SELECT or DML with host variables — that's what PREPARE + cursor is for.
G. Transaction control — COMMIT and ROLLBACK
By default, RPG programs run under
Key points on commit control:
COMMIT(*CS) onCTL-OPT enables cursor stability — reads don't lock, updates lock the row until commitCOMMIT(*CHG) locks only changed rows — lighter, but doesn't prevent non-repeatable readsCOMMIT(*NONE) means no transaction boundaries at all — fine for read-only programs, dangerous for multi-step writes- The journal (
JRN ) must be configured on the library/files —COMMIT without journaling gives you a negative SQLCODE at runtime - Named activation groups (
ACTGRP('ORDERAPP') ) give you a commit scope boundary.ACTGRP(*CALLER) shares the caller's commit scope, which can cause surprises when a calling program issues ROLLBACK
Key Takeaways
- Use embedded SQL for anything beyond simple keyed access — joins, aggregates, computed columns, filtered queries all belong in SQL, not chained READ loops
- Always check SQLCODE after every Exec SQL — 0 is success, 100 is not found, negative is an error. Don't just check for "not zero"
SQLERRD(3) is your row count — for INSERT, UPDATE, DELETE, check it. SQLCODE 0 doesn't mean rows were affected- Use parameter markers for dynamic SQL — never concatenate user values into SQL strings.
? placeholders prevent injection and improve plan reuse - NULL needs indicator variables — RPG has no NULL concept. The indicator variable (placed after the host variable, no comma) is the bridge
- COMMIT/ROLLBACK for multi-step writes — if two operations must succeed together, wrap them in a transaction. Set
COMMIT(*CS) on CTL-OPT and configure journaling on the files DOW 1 = 1 / LEAVE for cursor loops — avoids the priming-fetch problem and keeps the FETCH in one place- Compile with
CRTBNDRPG — it handles the SQL precompile automatically on 7.5 and 7.6
How do I use modern RPG data structures and arrays
Modern RPG makes extensive use of data structures and arrays to organize and manipulate business data efficiently. This section shows you how to define qualified data structures, create arrays of those structures, and—most importantly—populate them directly from your IBM i database using only embedded SQL statements. You will also see the newest array capabilities introduced in IBM i 7.4 and still available in 7.6: variable-dimension arrays. These techniques eliminate tedious looping and manual field-by-field assignment, reduce the chance of errors, and produce code that is dramatically easier to read and maintain.
The following topics are described in this section:
- Defining qualified data structures for business records
- The two main types of variable-dimension arrays:
*VAR and*AUTO - Creating and populating array data structures with SQL
- Using
EVAL-CORR to move data between differently named or typed structures - Sorting and searching arrays after population
- Best practices and performance considerations
Why modern data structures and arrays matter
A data structure in modern RPG is far more than a collection of fields. When you qualify it and combine it with arrays, you create a self-documenting container that mirrors the shape of your database tables or result sets. This approach brings three immediate benefits to any IBM i shop:
- Readability — Long, descriptive subfield names replace cryptic positions or numbered fields.
- Performance — SQL can fetch hundreds or thousands of rows into an array in a single operation, replacing slow RPG loops.
- Maintainability — Changes to a table column automatically flow through the data structure when you use
EXTNAME orLIKEDS .
As discussed in 7.15, “How to use embedded SQL,” modern RPG treats the database as a first-class citizen. Data structures and arrays are the natural bridge between that database and your business logic.
Defining qualified data structures
Start every modern data structure with the
You can define a data structure manually or base it directly on a database table or view with
Example - Data structure based directly on the CUSTOMER table
Any future change to the NAME column length is automatically reflected the next time you recompile.
The two types of variable-dimension arrays
IBM i 7.4 introduced variable-dimension arrays that remove the need to guess a safe fixed size. There are two flavors:
- *DIM(VAR : maximum) — The actual number of elements used is controlled by your program (you set it explicitly with
%ELEM ). This is ideal when you know exactly how many rows you want to fetch or when you want full control over the active size. - *DIM(AUTO : maximum) — The array grows automatically as you assign elements beyond the current size, up to the declared maximum. This is perfect for situations where the number of items is truly unknown until run time, such as processing JSON input or variable query results.
Both types are fully compatible with SQL multi-row FETCH and with EVAL-CORR.
Here are the two styles shown side-by-side using order data from the provided tables:
Notice the intentional differences in subfield names and data types. These differences are common when one structure is optimized for SQL fetch and another for host-program processing.
Populating array data structures with SQL
The real power appears when you combine these arrays with embedded SQL. You can load the entire array in one statement—no DO loops, no manual indexing.
Single-row fetch into a data structure
Multi-row fetch into a variable-dimension array
SQL can deliver rows directly into either
Moving data between structures with EVAL-CORR
When subfield names or types differ,
Complete working example
The following complete program demonstrates all the concepts in a realistic business scenario. It fetches aggregated order information from the ORDERS, CUSTOMER, and ORDERDTL tables into a
After the multi-row FETCH, the
- From
OrdersAutoDS toOrderFixedDS — theCustName field is skipped because the target usesName . - From
OrdersAutoDS toOrderVarDS —OrderTotal is skipped because the target field is defined asChar(20) instead ofZoned(11:2) .
For the
The program then demonstrates sorting with
Sorting and searching arrays
Once your array data structure is populated, you frequently need to reorder or locate specific entries without additional database calls. Modern RPG provides efficient built-in functions that operate directly on arrays of data structures.
These operations complete the modern array pattern: fetch once with SQL, move or transform with
Best practices and performance considerations
- Always qualify data structures and arrays.
- Use
EXTNAME orLIKEDS whenever possible so the structure stays synchronized with the database. - Prefer
*AUTO when the number of rows is truly variable; use*VAR when you want explicit control over the dimension. - Keep the maximum dimension generous but realistic.
- When you only need a subset of columns, declare the data structure with exactly those columns rather than using
SELECT * . - Test with realistic data volumes. A 20-row fetch is instantaneous; larger result sets may require commitment control or a larger SQL cursor hold setting.
- Combine these techniques with the fully free-format coding style covered in How do I write fully free format RPG?. The result is code that looks more like modern business applications written in any other language.
Shops that adopt array data structures populated by SQL routinely report 30–70% fewer lines of code in their data-retrieval routines and dramatically lower maintenance costs. New developers who know Java, Python, or JavaScript immediately recognize the pattern: a list of objects fetched from the database. The same business rules can now be reused from green-screen programs, REST services, or mobile front ends without duplication.
Modern RPG data structures and arrays are not cosmetic improvements. They are the foundation that lets you write concise, high-performance code that is easy to test, easy to hand off, and ready for whatever interface technology comes next.
How do I process JSONs within RPGLE programs?
What This Covers
This procedure covers the options available on IBM i to process JSON payloads.
What It Is
REST APIs, message queues, webhook callbacks — most integrations your RPG programs touch today exchange data as JSON. Your programs need to parse, extract from, and generate JSON.
Three options exist (this procedure focuses on SQL JSON functions — see the references at the end of this section for the other two):
For more info on the other approaches, check out the following references:
Why It Matters

If your RPG programs can't consume or produce JSON, you'll need middleware just to translate data formats - adding latency, another moving part, and a dependency on a team that may not understand your business logic.
Native JSON handling on IBM i eliminates that translation layer. Your RPG program receives a JSON order, shreds it into Db2 for i rows, processes the business rules, and returns a JSON response - all without leaving the platform. That's fewer hops, fewer failure points, and business logic that stays where your team knows it best.
There's a portability angle, too. These SQL/JSON functions are part of the ISO SQL standard, so the skills transfer across PostgreSQL, Oracle, and SQL Server - unlike DATA-INTO and YAJL, which are IBM i-specific.
How It Works
Use Case:
- An eCommerce application exchanges Orders with IBM i — all requests and responses are JSON (API payloads or event stream messages)
- We will demonstrate how to:
- A. Extract a single value with JSON_VALUE
- B. Parse a JSON payload into rows with JSON_TABLE
- C. Transform JSON arrays into relational tables
- D. Return JSON responses
- E. Read JSON from IFS files
Sample JSON payload for an Order
A. Extract Customer Email for validation purposes using JSON_VALUE
B. Parse the incoming JSON Payload and insert into Order Header and Order Detail tables
RPGLE Program to process the Order
Example B gives you row-by-row control when you need validation or transformation logic in RPG before inserting. If you just need a straight insert with no intermediate processing, Example C is simpler.
C. Extract the Product details and save in Order Details table using the JSON_TABLE function
D. Return JSON responses from RPGLE programs
Note: This RPG program would typically be wrapped in a stored procedure — the standard way to expose RPG business logic to external callers over SQL, HTTP, or ODBC/JDBC. The stored procedure provides a clean call interface, handles authorization, and supports returning result sets (for example, JSON responses) directly to the caller (in this case the eCommerce application).
E. Read JSON from IFS files
The job profile needs
Before passing
This is worth adding to any RPG program that accepts JSON from an external caller. Malformed JSON passed straight to
Key Takeaways
- Pick the right tool for the shape of your work — SQL JSON functions for Db2 for i-bound data, DATA-INTO for RPG variable processing
- Always specify CCSID 1208 on CLOB parameters receiving external JSON
- JSON_TABLE replaces pages of parsing logic with one SQL statement
- Wrap your RPG in a stored procedure — it provides the standard call interface for external consumers and supports returning JSON result sets
- Validate JSON before parsing — probe with
JSON_VALUE first;SQLCODE -171 means the input isn't valid JSON; catching it there gives you a clean error path beforeJSON_TABLE orJSON_ARRAYAGG see the data - IFS reads require IFS authority — the job profile needs
*R on the file and*RX on every parent directory; missing authority surfaces asSQL7064 , not a permission message