You already write RPG. You know what a file is, you’ve written READ and WRITE operations, and you’ve probably maintained your share of fixed-format code that looks like it was typed on a punch card — because it was designed exactly for that.
Free-format RPGLE doesn’t replace what you know. It just removes the straitjacket. No more counting columns. No more squeezing logic into 80 characters. No more cryptic two-letter opcodes that made sense in 1978.
This guide walks you through the shift — practically, with real code comparisons — so you can start writing modern RPGLE today.
What actually changed
Traditional fixed-format RPG forced you to put everything in specific columns. Indicators in columns 7-8, factor 1 in 12-25, opcode in 26-35, factor 2 in 36-49. Miss a column and you get a compile error that makes no sense.
Free-format throws all of that out. You write code the way you think — left to right, with proper keywords, in any column you like. The compiler figures out the rest.
Here is the same logic written both ways:
Fixed-format (old way)
C IF Salary > 50000
C EVAL TaxRate = 0.30
C ELSE
C EVAL TaxRate = 0.20
C ENDIFFree-format (modern way)
if Salary > 50000;
TaxRate = 0.30;
else;
TaxRate = 0.20;
endif;Same logic. One is readable. The other looks like it belongs in a museum.
How to enable free-format in your source
You need one control spec at the top of your source member. This tells the compiler you are writing free-format:
**FREEThat is it. Put **FREE on line 1, column 1, and the entire source member is now free-format. No mixed modes, no column restrictions.
If you are working in an older source member and cannot convert the whole thing yet, you can use the /FREE and /END-FREE directives to free-format individual sections — but for any new code you write, just use **FREE from the top.
The specs you will use every day
Control spec
**FREE
ctl-opt dftactgrp(*no) actgrp(*caller) option(*srcstmt *nodebugio);dftactgrp(*no) is important — it tells the system this program uses ILE activation groups, which you need for subprocedures and service programs. Always include it in new programs.
File spec
dcl-f EMPLOYEEPF disk(*ext) usage(*input) keyed;dcl-f declares a file. Much more readable than the old F-spec columns. You can see exactly what the file is doing — disk, external, input only, keyed access.
Data definitions
dcl-s EmployeeName varchar(50);
dcl-s Salary packed(9:2);
dcl-s TaxRate packed(5:4);
dcl-s Counter int(10) inz(0);
dcl-ds AddressDS;
Street varchar(100);
City varchar(50);
Pincode char(6);
end-ds;Control flow — if, dow, for
IF / ELSEIF / ELSE
if Department = 'SALES';
Bonus = Salary * 0.15;
elseif Department = 'IT';
Bonus = Salary * 0.12;
else;
Bonus = Salary * 0.08;
endif;DOW — Do While
dow not %eof(EMPLOYEEPF);
read EMPLOYEEPF;
if not %eof(EMPLOYEEPF);
TotalSalary += Salary;
Counter += 1;
endif;
enddo;FOR loop
for i = 1 to %elem(MonthlyArray);
AnnualTotal += MonthlyArray(i);
endfor;Built-in functions you will use constantly
// String handling
dcl-s FullName varchar(100);
FullName = %trimr(FirstName) + ' ' + %trimr(LastName);
// Numeric checks
if %dec(InputField: 7: 2) > 0;
// process it
endif;
// Date handling
dcl-s Today date(*iso);
Today = %date();
dcl-s DaysLeft int(5);
DaysLeft = %diff(DueDate: Today: *days);
// EOF and found checks
read EMPLOYEEPF;
if %eof(EMPLOYEEPF);
leave;
endif;
chain EmployeeID EMPLOYEEPF;
if %found(EMPLOYEEPF);
// record found
endif;Subprocedures — the big upgrade
If you are still writing everything in the main procedure, free-format is the right time to change that habit. Subprocedures let you break logic into reusable, testable chunks — the same idea as functions in any modern language.
**FREE
ctl-opt dftactgrp(*no) actgrp(*caller);
dcl-s EmpSalary packed(9:2);
dcl-s FinalTax packed(9:2);
EmpSalary = 75000;
FinalTax = CalcTax(EmpSalary);
dsply FinalTax;
*inlr = *on;
return;
dcl-proc CalcTax;
dcl-pi *n packed(9:2);
pSalary packed(9:2) value;
end-pi;
dcl-s TaxAmount packed(9:2);
if pSalary > 50000;
TaxAmount = pSalary * 0.30;
else;
TaxAmount = pSalary * 0.20;
endif;
return TaxAmount;
end-proc;A complete working example
**FREE
ctl-opt dftactgrp(*no) actgrp(*caller) option(*srcstmt);
dcl-f EMPLOYEEPF disk(*ext) usage(*input);
dcl-s TotalPayroll packed(13:2) inz(0);
dcl-s EmpCount int(10) inz(0);
dcl-s AvgSalary packed(11:2);
read EMPLOYEEPF;
dow not %eof(EMPLOYEEPF);
TotalPayroll += SALARY;
EmpCount += 1;
read EMPLOYEEPF;
enddo;
if EmpCount > 0;
AvgSalary = TotalPayroll / EmpCount;
endif;
dsply ('Total Payroll: ' + %char(TotalPayroll));
dsply ('Employees: ' + %char(EmpCount));
dsply ('Avg Salary: ' + %char(AvgSalary));
*inlr = *on;
return;Common mistakes when switching
Forgetting the semicolon. Every statement ends with a semicolon in free-format. Coming from fixed-format this trips everyone up in the first week. The compiler error will say something unhelpful — just look for a missing ;.
Mixing free and fixed in the same spec. If you use **FREE at the top, the entire source is free-format. You cannot go back to fixed columns mid-member without the directives.
Leaving out dftactgrp(*no). If you try to call a subprocedure or use dcl-proc without this in your control spec, you will get a compile error about activation groups. Always include it.
Using old opcodes out of habit. EVAL, MOVE, MOVEL — these work in free-format but they are the old way. Replace EVAL X = Y with just X = Y. Replace MOVE with proper assignment or %subst.
What to do right now
Do not try to convert your entire codebase. Pick one small utility program — something you understand completely — and rewrite it in free-format with **FREE. Keep the original. Compare them. Compile and test the new one.
That first rewrite is where it clicks. You will spend the next hour wondering why you did not switch sooner.
The next article in this series covers subprocedures and service programs — how to build reusable libraries of RPGLE code the right way. If you are writing the same logic in three different programs, that is the article you need.