← All cases

08 · Case study

RPG on IBM i to Java

An IBM i (AS/400) estateRPGLE · SQLRPGLE · DDS · DB2 for i · Java 21

The system

Source members
RPGLE · SQLRPGLE · RPG III · CL
Screens
display files, subfiles
Target
Java 21 · Spring Boot
Packed decimal
BigDecimal, gated

The problem

An IBM i estate is usually the system the business actually runs on and the one nobody will touch. The programs work. The people who wrote them have retired. The source is there, in fixed-format columns for the older members and free-form for the newer ones, and between the two sit twenty years of maintenance in whichever style was current that year.

What makes RPG hard to read is not the syntax. It is that the control flow is partly invisible: indicators set in one place and tested in another, the program cycle driving the older members without a single explicit loop, and record-level access — CHAIN, SETLL, READE — that reads the database a row at a time against a key path defined in a different object entirely. Read the program alone and you have half the behaviour.

What ran

  1. Parse the members, all the dialects

    A deterministic scanner in the playbook reads fixed-format RPG III and IV alongside free-form RPGLE, resolves /COPY members, and records the specifications as facts: files and their access paths, data structures with their packed and zoned fields, subprocedures and their signatures, every CALL and CALLP with its target, and the EXEC SQL statements embedded in SQLRPGLE.

  2. Make the indicators and the cycle explicit

    An indicator that is set in one specification and tested forty lines later becomes a named boolean with the meaning the code gives it. Where an older member relies on the RPG cycle rather than writing its loop, the loop it implies is written out. This is the step that turns a member into something a Java developer can review, and it is done by the parser rather than by a model, so it is the same every time.

  3. Follow the access paths into the data

    A CHAIN is a keyed read against a file whose key is declared in a DDS logical file, so the access path is resolved from the DDS rather than guessed from the field names. Record-level operations become repository queries against those keys. The database side of this is its own migration, and the two share one parse of the DDS.

  4. Decide what happens to the green screens

    A display file with subfiles is a paging list with function keys and field attributes driven by indicators. The questionnaire asks what it should become: an HTTP API with a new front end, a service left behind the existing 5250 screens during a phased cutover, or a batch entry point where the screen was only ever a launcher. The migration does not assume a web UI is wanted.

  5. Translate one member at a time, compiling as you go

    Each member is emitted and the project built before the next one starts. A partial run leaves a project that compiles, and a failure names the member behind it.

  6. Gate on decimals, dates and dead paths

    Packed and zoned decimal fields become BigDecimal, never double — the gate that already blocks a COBOL to Java cutover applies unchanged here. Every file the program opens is accounted for, every called program resolves to something that exists or is listed as external, and the project builds and its tests run.

What it did

A layered service instead of a member

Programs become services, subprocedures become methods, and file access moves behind repositories. The layering is the point: an RPG member mixes screen handling, business rules and database access in one object, and until those are separated nothing about the system can be tested in isolation.

The call graph across the estate

Which programs call which, which service programs they bind to, and which files each one touches — recovered from the source rather than from a spreadsheet someone maintained until 2014. This is what tells you which members can move first.

A named list of what needs a person

Machine interfaces, data areas, message queues, activation-group behaviour and any operation whose Java equivalent is not provable are listed rather than approximated. They are usually a small number of members, and they are the ones worth a conversation.

Where it ended

Whether the screens survive. Every AS/400 modernization eventually reaches the question of whether the 5250 interface is a liability or the reason the warehouse staff are fast, and that is a business answer rather than a technical one. The migration is built so the answer can be either: the service layer is the same whether a browser or a green screen sits in front of it.

What happens next

  • Run the database migration first. The programs need a schema to be migrated against, and the two share one parse of the DDS.
  • CL programs are orchestration rather than application logic and are handled as such — job streams become scheduled jobs, not Java classes.
  • Decide about the green screens. The service layer is the same whether a browser or a 5250 session sits in front of it, so the answer can wait — but it should be a decision rather than a default.

The reason to parse rather than prompt is that RPG hides its control flow in state. A model reading a member in isolation will produce plausible Java that is subtly wrong about an indicator, and plausible-but-wrong is the expensive kind. Recovering the indicators, the cycle and the access paths deterministically means the same member produces the same Java every time, and a person can check it against the original line by line.