Guru: From Hiking Boots To Long-Term Memory
November 9, 2026 Gregory Simmons
In my last article, “Environment Variables: Global Variables Wearing Hiking Boots,” we explored how environment variables can carry information around an IBM i job, making it available to programs and procedures along the way.
But what if our variables don’t need to go hiking? What if we simply need them to remember where they’ve been? As RPG developers, we’re accustomed to declaring variables, assigning values, and expecting those values to be there when we need them. But have you ever considered what happens to those variables when a procedure ends? Or what happens when you call that same procedure again?
Some variables start fresh every time. Others have surprisingly good memories. Let’s head back into the mushroom patch and see what happens.
Some Mushrooms Have Better Memories Than Others
Consider two simple procedures. Both count mushrooms, but there’s one small difference in how their variables are declared.
Our first procedure uses an ordinary local variable:
dcl-proc countAutomatic; dcl-pi *n int(10); end-pi; dcl-s mushrooms int(10) inz(0); mushrooms += 1; return mushrooms; end-proc;
Every time we call countAutomatic(), the variable mushrooms is initialized to zero. We increment it and return 1.
Call the procedure three times, and we get:
1 1 1
Now let’s make one small change:
dcl-proc countStatic; dcl-pi *n int(10); end-pi; dcl-s mushrooms int(10) inz(0) static; mushrooms += 1; return mushrooms; end-proc;
We’ve added the STATIC keyword. Call this procedure three times, and the results are quite different:
1 2 3
Instead of initializing mushrooms every time the procedure is called, RPG initializes the static variable when its containing program or service program is activated. Its value is retained between calls. Notice that the variable is still local to the procedure. No other procedure can directly access mushrooms, even though its value survives after the procedure returns.
And here’s an important distinction: static doesn’t mean global. Global describes where a variable can be accessed, while static describes how its storage is maintained. Global RPG variables generally have static storage, but our mushrooms variable is both local and static. So far, nothing particularly surprising. But let’s see what happens when we introduce a service program.
When Mushrooms Become A Service
Suppose we create a module named MUSHSRV containing an exported procedure called COUNTMUSHROOMS():
**free ctl-opt nomain; dcl-proc COUNTMUSHROOMS export; dcl-pi *n int(10); end-pi; dcl-s mushrooms int(10) inz(0) static; mushrooms += 1; return mushrooms; end-proc;
Nothing complicated here. Every time the procedure is called, it increments its static counter and returns the result.
Now we will create two programs, MUSHMAIN and MUSHOTHER, that call this same procedure. Each program also has its own local static counter so we can compare the behavior.
Here’s MUSHMAIN:
**free
dcl-pr COUNTMUSHROOMS int(10);
end-pr;
dcl-s localCount int(10);
dcl-s serviceCount int(10);
localCount = countLocal();
serviceCount = COUNTMUSHROOMS();
dsply ('MAIN local: ' + %char(localCount));
dsply ('MAIN service: ' + %char(serviceCount));
return;
dcl-proc countLocal;
dcl-pi *n int(10);
end-pi;
dcl-s mushrooms int(10) inz(0) static;
mushrooms += 1;
return mushrooms;
end-proc;
Our second program, MUSHOTHER, uses identical logic, except that OTHER replaces MAIN in the display statements.
**free
dcl-pr COUNTMUSHROOMS int(10);
end-pr;
dcl-s localCount int(10);
dcl-s serviceCount int(10);
localCount = countLocal();
serviceCount = COUNTMUSHROOMS();
dsply ('OTHER local: ' + %char(localCount));
dsply ('OTHER service: ' + %char(serviceCount));
return;
dcl-proc countLocal;
dcl-pi *n int(10);
end-pi;
dcl-s mushrooms int(10) inz(0) static;
mushrooms += 1;
return mushrooms;
end-proc;
Same Service Program, Different Memories
Here’s where things get interesting. We’ll create the service program with ACTGRP(*CALLER) and place our two calling programs in different named activation groups.
For these examples, we’ll use a test library named MUSHLIB, with the source members stored in QRPGLESRC.
First, compile the module and create the service program:
CRTRPGMOD MODULE(MUSHLIB/MUSHSRV) SRCFILE(MUSHLIB/QRPGLESRC)
CRTSRVPGM SRVPGM(MUSHLIB/MUSHSRVC) MODULE(MUSHLIB/MUSHSRV) EXPORT(*ALL)
ACTGRP(*CALLER)
Next, create the two calling programs:
CRTRPGMOD MODULE(MUSHLIB/MUSHMAIN) SRCFILE(MUSHLIB/QRPGLESRC)
CRTPGM PGM(MUSHLIB/MUSHMAIN) MODULE(MUSHLIB/MUSHMAIN)
BNDSRVPGM(MUSHLIB/MUSHSRVC) ACTGRP(MUSHGRPA)
CRTRPGMOD MODULE(MUSHLIB/MUSHOTHER) SRCFILE(MUSHLIB/QRPGLESRC)
CRTPGM PGM(MUSHLIB/MUSHOTHER) MODULE(MUSHLIB/MUSHOTHER)
BNDSRVPGM(MUSHLIB/MUSHSRVC) ACTGRP(MUSHGRPB)
Notice that we have placed MUSHMAIN in activation group MUSHGRPA and MUSHOTHER in MUSHGRPB. Because these are named activation groups, their activations remain available between calls, allowing their static counters to retain their values.
Now let’s call the programs in this order, using the same IBM i job:
CALL MUSHLIB/MUSHMAIN CALL MUSHLIB/MUSHMAIN CALL MUSHLIB/MUSHOTHER CALL MUSHLIB/MUSHMAIN CALL MUSHLIB/MUSHOTHER
Here’s what we should see:
Program Local Counter Service Counter MUSHMAIN 1 1 MUSHMAIN 2 2 MUSHOTHER 1 1 MUSHMAIN 3 3 MUSHOTHER 2 2
Wait a minute. Both programs are calling the same service program, yet the service counter started over when MUSHOTHER made its first call! That’s because we created the service program with ACTGRP(*CALLER).
When MUSHMAIN calls the service program, it is activated in MUSHGRPA. When MUSHOTHER calls it, another activation is created in MUSHGRPB. Each activation has its own static storage, including its own copy of mushrooms. In other words, our service program has two separate memories.
Let’s Give Those Mushrooms A Shared Memory
Instead of creating our service program with ACTGRP(*CALLER), we’ll give it its own named activation group. To make the comparison easier, we’ll create a second service program named MUSHSRVS, using the same RPG module. This lets us compare the two configurations without replacing our original service program.
CRTSRVPGM SRVPGM(MUSHLIB/MUSHSRVS) MODULE(MUSHLIB/MUSHSRV)
EXPORT(*ALL) ACTGRP(MUSHSHARE)
We will create two new program objects from our existing caller modules, binding them to the new service program.
CRTPGM PGM(MUSHLIB/MUSHMN2) MODULE(MUSHLIB/MUSHMAIN)
BNDSRVPGM(MUSHLIB/MUSHSRVS) ACTGRP(MUSHGRPA)
CRTPGM PGM(MUSHLIB/MUSHOT2) MODULE(MUSHLIB/MUSHOTHER)
BNDSRVPGM(MUSHLIB/MUSHSRVS) ACTGRP(MUSHGRPB)
Before running this second experiment, reclaim the two caller activation groups so we start with a clean slate.
RCLACTGRP ACTGRP(MUSHGRPA) RCLACTGRP ACTGRP(MUSHGRPB)
Now repeat the experiment using our new program objects:
CALL MUSHLIB/MUSHMN2 CALL MUSHLIB/MUSHMN2 CALL MUSHLIB/MUSHOT2 CALL MUSHLIB/MUSHMN2 CALL MUSHLIB/MUSHOT2
You should see the following results:
Program Local Counter Service Counter MUSHMN2 1 1 MUSHMN2 2 2 MUSHOT2 1 3 MUSHMN2 3 4 MUSHOT2 2 5
Now that’s interesting!
The local counters still maintain their own values, but both programs are incrementing the same service program counter. We haven’t changed a single line of RPG source code. All we’ve changed is the activation group in which our service program runs.
With ACTGRP(MUSHSHARE), both callers use the same service program activation within the job. That means they also use the same static mushroom counter. The STATIC keyword hasn’t changed its behavior. What changed is which activation owns that static storage.
But Don’t Forget About the Job
Before you start thinking you’ve discovered a clever way to share information throughout your IBM i system, there’s one more thing to remember. Activation groups belong to jobs. If we run MUSHMN2 in one job and MUSHOT2 in another, each job has its own MUSHSHARE activation group and its own copy of the static mushroom counter. The fact that both activation groups have the same name doesn’t mean they share storage across jobs.
That’s an important difference from the system-level environment variables we discussed in the previous article, which can make named values available to multiple jobs. And if we end an activation group and subsequently reactivate the service program, its static variables are initialized again. Long-term memory doesn’t necessarily mean permanent memory.
When Is STATIC Useful?
Static local variables can be handy when a procedure needs to remember something between calls without exposing that information as a global variable.
Perhaps you are counting how many times a procedure has been called, retaining a previously calculated value, or maintaining some internal state that belongs to a service program. But be aware, a procedure that retains information between calls may behave differently depending on what happened earlier in the job. That can make troubleshooting more difficult, especially when multiple programs (in the same job) use the same service program. And if your application uses multiple threads, shared static storage introduces additional considerations for synchronization and thread safety. As with many RPG features, understanding when to use STATIC is just as important as understanding how it works.
Back To Our Hiking Boots
In my previous article, environment variables gave us a way to carry information around our IBM i jobs. This time, we’ve discovered that some RPG variables don’t need to travel anywhere. They can remain right where they are, remembering what happened the last time we visited. The trick is understanding where that memory lives, who can access it, and what causes it to disappear. After all, there’s nothing wrong with having a good memory.
Unless, of course, your RPG program remembers something you expected it to forget.
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: Environment Variables – Global Variables Wearing Hiking Boots
Guru: When RNF0722 Won’t Let Your Legendary RPG Program Compile
Guru: Two More Small But Handy RPG Enhancements: LIKE(*EXT) And %LOOKUPNE
Guru: Beyond Three-Part Naming – Running SQL Across Remote IBM i Systems
Guru: Finding Data In The Forest – Exploring Three-Part Naming In SQL
Guru: SQL Sequences In RPG Let Db2 Handle The Counting
Guru: IBM i Job Log Detective Brings Structure To Job Log Analysis In VS Code
Guru: Managing The Lifecycle Of Your Service Programs – Updates Without Chaos
Guru: Are Binding Directories A Shortcut Or A Source Of Chaos?
Guru: Service Programs And Activation Groups – Design Decisions That Matter
Guru: Binder Source Is Your Service Program’s Owner’s Manual
Guru: Access Client Solutions 1.1.9.11 – Security First, With Continued Investment In SQL Tooling
Guru: Taming The CRTSRVPGM Command – Options That Can Save Your Sanity
Guru: CRTSRVPGM Parameters That Can Save or Sink You
Guru: A First Look at Bob, The IBM i Assistant That’s Closer Than You Think
Bob More Than Just A Code Assistant, IBM i Chief Architect Will Says
IBM Pulls The Curtain Back A Smidge On Project Bob
Big Blue Converges IBM i RPG And System Z COBOL Code Assistants Into “Project Bob”
Guru: When Attention Turns To You – Writing Your Own ATTN Program
Guru: WCA4i And Granite – Because You’ve Got Bigger Things To Build
Guru: When Procedure Driven RPG Really Works
Guru: Unlocking The Power Of %CONCAT And %CONCATARR In RPG
Guru: AI Pair Programming In RPG With Continue
Guru: AI Pair Programming In RPG With GitHub Copilot
Guru: RPG Receives Enumerator Operator
Guru: RPG Select Operation Gets Some Sweet Upgrades
Guru: Growing A More Productive Team With Procedure Driven RPG
Guru: With Procedure Driven RPG, Be Precise With Options(*Exact)
Guru: Testing URLs With HTTP_GET_VERBOSE
Guru: Fooling Around With SQL And RPG
Guru: Procedure Driven RPG And Adopting The Pillars Of Object-Oriented Programming
Guru: Getting Started With The Code 4 i Extension Within VS Code
Guru: Procedure Driven RPG Means Keeping Your Variables Local
Guru: Procedure Driven RPG With Linear-Main Programs
Guru: Speeding Up RPG By Reducing I/O Operations, Part 2
Guru: Speeding Up RPG By Reducing I/O Operations, Part 1
Guru: Watch Out For This Pitfall When Working With Integer Columns

