Guru: CRTSRVPGM Parameters That Can Save or Sink You
January 12, 2026 Gregory Simmons
It was 2 a.m., and the phone wouldn’t stop ringing. A production job had failed, and the error logs weren’t making any sense. After an hour of digging, the culprit finally revealed itself: the wrong procedure had been bound in a service program. Somehow, a silent duplicate had snuck through, and everything downstream was broken. If you’ve ever been in that situation, you know that “it compiles, it runs” is the worst mindset you can have when creating service programs. The parameters on CRTSRVPGM exist for a reason, and ignoring them can turn a simple build into a nightmare at the worst possible hour.
When it comes to service programs, too many developers fall into the trap of running CRTSRVPGM with whatever defaults IBM gives them. It compiles, it runs, and that’s all they care about – until something breaks in production, and suddenly everyone’s scrambling to figure out which procedure the system actually bound. Service programs are a core piece of modern RPG design, but if you don’t take their creation seriously, you’re setting yourself up for a maintenance nightmare. Let’s look at a few of the most important parameters on CRTSRVPGM and talk about why they matter more than most people think.
Let’s start with binding directories. On paper, they are a gift. You put all your commonly used modules or service programs in one place, specify that directory on the create command, and you don’t have to remember the names of everything you are binding. That kind of convenience is tempting, especially in a large application with hundreds of modules. The problem is that this same convenience hides what’s actually going on. If you have several binding directories chained together, you can end up binding to the wrong version of a procedure without realizing it. Suddenly you’re calling last year’s code, and no one can figure out why your new logic isn’t running.
My rule of thumb? Use binding directories intentionally. By that, I mean each service program should have its own binding directory which ONLY contains the modules it needs. A well-documented directory for your utility routines is fine, and it’s reasonable to have one for your business logic too. But be careful. Explicitly specifying what you’re binding to may take more time up front, but it also means there’s no mystery about where your procedures are coming from.
Another parameter that deserves attention is OPTION. By default, this parameter is blank, which sounds harmless enough, but that blank actually means you’re allowing both duplicate procedures (*DUPPROC) and duplicate variables (*DUPVAR). That’s a quiet landmine. If you allow duplicates, the system won’t complain when you have two procedures or variables with the same name in different modules – it will just pick one, and it may not be the one you expect. I’ve seen teams waste hours debugging only to discover they were calling the wrong version of a procedure simply because the wrong one got bound. The fix is simple: explicitly set OPTION(*NODUPPROC *NODUPVAR) and make it a shop-wide standard. If you have naming conflicts, fix them now. You will save yourself endless headaches later.
Then there’s the question of what to export. If you take the easy way out and use EXPORT(*ALL), you’re exposing every single procedure in your service program, including the little internal helpers you never intended anyone else to use. That’s like giving every guest in your house a key to your tool shed – it might work, but you are going to regret it when someone “borrows” something and breaks it. A better approach is to control what’s exported with binder source. By using EXPORT(*SRCFILE) and explicitly listing what’s public, you lock down your service program’s interface and keep internal routines private where they belong.
In my current shop, we have also adopted a naming convention that reinforces this principle. Every exported procedure begins with the name of the service program that owns it. For example, if we had a service program full of mushroom-related utilities called mshrmutils, it would export procedures like mshrmutils_save_new_species, mshrmutils_is_edible, and so on. We use the same convention for any global variables, although global variables themselves should be used sparingly – local variables are almost always the better choice. (For a deeper dive on why local variables are preferred, see my earlier article, Procedure-Driven RPG Means Keeping Your Variables Local.) This simple rule delivers two big benefits. First, you’ll never accidentally create two procedures or variables with the same name in different service programs, which makes OPTION(*NODUPPROC *NODUPVAR) easy to enforce. Second, when you’re reading code and see a call to a procedure, the first word of the procedure or variable name tells you exactly which service program it comes from. That means less digging, faster troubleshooting, and fewer “where is this thing defined?” moments.
The bottom line is this: CRTSRVPGM is not a “compile it and forget it” command. Every parameter on that command exists for a reason, and understanding them will save you from painful surprises. Be deliberate about binding directories. Refuse to allow duplicate procedures and variables. Lock down what you export. Getting lazy with these settings won’t bite you right away, but when it does, it will hurt. Do the work now, and your service programs will be predictable, maintainable, and a lot less stressful to manage.
In the next installment, we will dig deeper into binder source and explore how you can use it not just for export control but also for managing versioning in a way that makes your life easier, not harder.
Until next time, happy coding.
Gregory Simmons is a Project Manager with PC Richard & Son. He started on the IBM i platform in 1994, graduated with a degree in Computer Information Systems in 1997 and has been working on the OS/400 and IBM i platform ever since. He has been a registered instructor with the IBM Academic Initiative since 2007, an IBM Champion and holds a COMMON Application Developer certification. When he’s not trying to figure out how to speed up legacy programs, he enjoys speaking at technical conferences, running, backpacking, hunting, and fishing.
RELATED STORIES
Guru: A First Look at Bob, The IBM i Assistant That’s Closer Than You Think
Bob More Than Just A Code Assistant, IBM i Chief Architect Will Says
IBM Pulls The Curtain Back A Smidge On Project Bob
Big Blue Converges IBM i RPG And System Z COBOL Code Assistants Into “Project Bob”
Guru: When Attention Turns To You – Writing Your Own ATTN Program
Guru: WCA4i And Granite – Because You’ve Got Bigger Things To Build
Guru: When Procedure Driven RPG Really Works
Guru: Unlocking The Power Of %CONCAT And %CONCATARR In RPG
Guru: AI Pair Programming In RPG With Continue
Guru: AI Pair Programming In RPG With GitHub Copilot
Guru: RPG Receives Enumerator Operator
Guru: RPG Select Operation Gets Some Sweet Upgrades
Guru: Growing A More Productive Team With Procedure Driven RPG
Guru: With Procedure Driven RPG, Be Precise With Options(*Exact)
Guru: Testing URLs With HTTP_GET_VERBOSE
Guru: Fooling Around With SQL And RPG
Guru: Procedure Driven RPG And Adopting The Pillars Of Object-Oriented Programming
Guru: Getting Started With The Code 4 i Extension Within VS Code
Guru: Procedure Driven RPG Means Keeping Your Variables Local
Guru: Procedure Driven RPG With Linear-Main Programs
Guru: Speeding Up RPG By Reducing I/O Operations, Part 2
Guru: Speeding Up RPG By Reducing I/O Operations, Part 1
Guru: Watch Out For This Pitfall When Working With Integer Columns

