Skip to content
Aug 14, 2026·8 min read

How an AS/400 migration moves more than an ERP

An AS/400 migration must preserve database semantics, RPG behavior, display file rules, batch work, security, and operational evidence.

How an AS/400 migration moves more than an ERP

Calling the AS/400 an ERP is a category error, and it leads teams to scope the wrong project. An ERP is an application. IBM i is an operating environment that may host bought packages, custom RPG applications, a database, screens, queues, reports, security rules, and the routines that connect all of them. A company can replace the package it recognizes and still leave the business trapped on the machine.

An AS/400 migration succeeds only when the replacement reproduces the business outcomes that the current environment produces, including the odd paths nobody wrote down. Moving tables and rewriting visible programs covers a fraction of that job. The hard work is finding where behavior lives, deciding which behavior deserves to survive, and proving that the new system agrees with production before the old one stops.

The useful unit of analysis is not an application name or a source member. It is a business transaction and every object that transaction touches. Follow an order release, a credit hold, a warehouse adjustment, or a month-end posting through screens, programs, files, queues, printed output, and operator intervention. That path tells you what must move.

The AS/400 label hides an operating environment

The machine usually holds a network of applications and operating conventions, not one ERP-shaped block. IBM renamed the platform several times, and IBM i now runs on Power hardware, but many companies still use AS/400 as the practical name for everything behind a green screen. That shorthand is harmless until it becomes the migration boundary.

Start with the object model. Programs, database files, display files, printer files, commands, data areas, data queues, job queues, output queues, user profiles, and authorization lists are system objects with types and authorities. Libraries group objects, while a job's library list influences which unqualified name resolves at runtime. Two jobs can call the same program name and reach different objects because their library lists differ. A repository search cannot show that by itself.

Bought software complicates the picture. A vendor package may own the accounting core while custom RPG code performs pricing, allocation, regulatory checks, warehouse work, or file exchange. CL programs may prepare the environment, override files, submit work, and call both vendor and custom programs. Operators may run commands from menus in a precise order because an old exception path never received a proper interface. None of that becomes part of the ERP merely because users enter through the same menu.

The first boundary to draw is between platform, package, customization, and integration. Record which party owns each component, how it is invoked, and what data or side effect it controls. This distinction changes the options. You might replace a package, retain a database temporarily, rewrite custom workflows, and retire a transfer protocol on separate schedules. Treating the whole environment as one product forces a risky all-at-once decision or leaves hidden dependencies until cutover.

Architecture diagrams often omit operational objects because they do not look like source code. That is a mistake. A job description can choose the library list, output queue, logging level, and user identity for submitted work. A data area may hold a processing date or sequence value. A message queue may be the only place an operator learns that a batch stopped. If a business process needs an object, that object belongs in the application map.

Db2 for i is part of application behavior

A database export does not capture Db2 for i semantics, because programs depend on more than rows and columns. Db2 for i is integrated with IBM i. Traditional applications commonly use DDS-created physical and logical files through native record access, while newer or revised code may use SQL tables, views, indexes, procedures, and triggers. Both styles can coexist in the same transaction.

IBM's Database files documentation makes a precise distinction: a physical file stores application data, while a logical file represents one or more physical files without storing another copy of the records. From an SQL viewpoint, a physical file resembles a table. A logical file can resemble a view, an index, or both. That last point matters. A logical file may define a keyed access path, select or omit records, reorder fields, join physical files, or present a record format that a program expects. Converting every logical file into a PostgreSQL view can remove an ordering or lookup property that native RPG code relied on. Converting every one into an index can lose selection and projection rules.

Members create another mismatch. A physical file can contain multiple members, each a separate set of records with the same format. Many systems use only the default member, but some use members for periods, branches, imports, or work sets. A flat export that reads one member silently loses data. A target design must decide whether a member becomes a partition, a tenant or period column, a staging table, or an obsolete convention. The decision requires usage evidence, not a blanket mapping.

Record formats also carry data assumptions. Packed decimals, zoned decimals, fixed length character fields, blank values, zero dates, coded flags, and externally described fields behave differently from casual SQL types. A ten digit number may be an identifier that must never receive arithmetic. A blank may mean unknown while zero means explicitly none. Some files have no database constraint because every writer historically enforced the rule in RPG. The migration must recover that rule before adding a target constraint, or legitimate old records will fail during load.

Journaling and commitment control need separate inspection. IBM's Commitment control documentation says that a group of database changes can commit or roll back as one unit, and that files used under commitment control must be journaled. Some application paths use those boundaries; others perform independent native writes and rely on restart logic. Wrapping a whole replacement request in a transaction may change lock duration and failure recovery. Failing to create a transaction where the old job had one can expose partial updates. Reproduce the observed unit of work first, then improve it deliberately.

Database discovery therefore needs file definitions, members, access paths, constraints, triggers, journals, record counts, authorities, and actual access traces. The schema is only the beginning.

RPG source is only part of the executable graph

RPG programs reveal calculations, but the executable graph also includes compiled descriptions, call conventions, CL setup, bound procedures, overrides, and runtime object resolution. A clean source inventory can still miss the code that production runs.

IBM's RPG documentation distinguishes program-described files from externally described files. With an externally described file, the compiler retrieves field and record definitions from DDS or SQL definitions at compile time. That creates a dependency that is easy to overlook: changing a file format can require dependent programs to be recompiled even when their source did not change. Level checks may catch an incompatible format, but disabling a level check does not make the layouts compatible. It only removes one warning mechanism.

Old RPG may use the cycle, indicators, data structures, subroutines, exception indicators, and native operations such as CHAIN, SETLL, READE, WRITE, UPDATE, and DELETE. Modern RPG may use free form syntax, procedures, service programs, and embedded SQL. Neither syntax tells you the business importance of a program. A thirty-line CL wrapper that sets a library list and overrides a database file can change the meaning of a ten-thousand-line RPG program.

Runtime binding deserves its own map. Record every call edge you can observe, including dynamic program names assembled from data, menu commands, exit programs, triggers, and remote invocations. Record parameters by position, type, length, and mutation behavior. IBM i programs often pass parameters by reference, so a callee can return status by changing an input field. A replacement API that models only the visible return value can discard error details or continuation state.

File overrides are a frequent source of migration defects. An OVRDBF command can redirect a program's file name to another file or member without editing the RPG source. That mechanism supports test data, alternate companies, archival members, and temporary work. If discovery reads only F specifications and SQL statements, it records the declared target rather than the object opened at runtime. Capture overrides from CL and active jobs, then link them to the transactions that use them.

Do the same for library lists and authorities. A program may run under the caller's profile, adopt its owner's authority, or depend on an authorization list. The target service needs an explicit identity and permission model. Copying broad IBM i privileges into one database account may make the first test pass, but it removes the separation that production quietly depended on.

A rewrite is safe when each production entry point has a traced path through this graph. Source translation without that graph produces convincing code that calls the wrong thing.

Display files contain business decisions

A display file is executable interface behavior, not a cosmetic screen definition. DDS display files define record formats, fields, constants, function keys, subfiles, attributes, validation keywords, and indicators that control what a user can see or enter. RPG and the display file divide responsibility for the interaction. Migrating only the RPG half changes the workflow.

Consider an order approval screen. RPG may load the order and set indicator 31 when the customer exceeds a credit threshold. The display file can use that indicator to expose a warning, protect an amount field, color a status, or enable a function key. Another indicator may mark invalid input and position the cursor on the failing field. The RPG source shows the indicator changing; the DDS explains what that change means to the operator. Without both, a new web screen may allow an edit that the old screen blocked.

Subfiles add stateful behavior. A program may load one page, keep a relative record number, mark changed rows, and process only records whose selection field changed. Function keys can have meanings that vary by record format. Error messages can come from message files rather than literal strings. Field reference files can supply lengths and validation rules shared across many screens. A visual screenshot records the appearance of one state, not the rules connecting states.

IBM's DDS rules say that option indicators or condition names can condition a keyword, field, or field location. That small sentence explains why automated screen conversion often disappoints. The interface logic is distributed across RPG state and DDS conditions. A screen parser must produce a state model, not a static form.

Walk the failure path, not just the happy path. Enter a closed customer, an invalid warehouse, a quantity above availability, and a function key during a partially completed update. Record the displayed message, cursor location, protected fields, database reads, writes, locks, and next screen. Then repeat with two sessions touching the same record. Operators often depend on the exact recovery behavior even when nobody calls it a requirement.

The replacement need not imitate a green screen pixel for pixel. It should preserve the decisions and controls, then use a client design that makes them clear. Separate field validation from domain policy. Keep keyboard paths where throughput depends on them. Replace cryptic indicators with named state. A prettier form that weakens the approval boundary is a regression.

Batch jobs and printed output are production interfaces

Test against production behavior
CodeHero checks rewritten transactions with a parity harness built from recorded production traffic.

Interactive screens show only the daytime edge of many IBM i systems. Batch jobs, queues, schedulers, spool files, transfers, and operator messages often perform settlement, replenishment, invoicing, reporting, and partner exchange after users leave. These interfaces need the same migration discipline as an API.

IBM's work management documentation describes how submitted jobs wait on job queues and run in subsystems with assigned resources and processing instructions. A job description can control routing and output. That means a batch program's behavior depends on its launch context. Running the same call interactively for a test may use a different library list, user profile, message handling rule, date, or output queue. The test can pass while the scheduled job still fails.

Inventory every scheduler, submitted job, subsystem, job queue, job description, routing entry, output queue, and monitored message queue associated with the business scope. Include external schedulers and scripts that connect over FTP, SFTP, database protocols, or command interfaces. Record calendars, dependencies, retry rules, concurrency limits, expected duration ranges, and the person or system that responds to failure. Do not infer a schedule from source comments. Observe it.

Spooled output deserves special attention. A printer file can encode headings, totals, overflow behavior, page breaks, copies, and routing. The physical printer may be gone while another process still consumes the spool as a document or archive feed. Ask who receives each output, what they do with it, and what proves delivery. Replacing a report with a web page is not equivalent if a warehouse expects a label stream or a bank expects a fixed record file.

Batch restart behavior is another hidden contract. A job may clear a work file, process records in key order, checkpoint with a data area, and resubmit itself after a recoverable message. Another may be intentionally safe to rerun because it checks a posted flag. If the replacement uses an at-least-once queue, you must know which operations are idempotent and which require a stable business key. A duplicate invoice is not an infrastructure detail.

Treat time as input. Processing dates, fiscal periods, daylight saving changes, holiday calendars, and end-of-day switches may come from data areas or control files rather than the system clock. A cloud scheduler with the right wall time can still produce the wrong business date.

Discovery must produce a behavior map

A useful discovery phase produces a queryable behavior map that connects entry points, objects, data, side effects, and operators. A spreadsheet containing program names and line counts cannot answer whether a credit release triggers a print, a queue message, and a second update through a called program.

Start with system inventories, then reconcile them with source and runtime evidence. The following IBM i commands create outfile data that can be queried instead of copied from terminal screens:

DSPOBJD OBJ(APP/*ALL) OBJTYPE(*ALL) OUTPUT(*OUTFILE) OUTFILE(AUDIT/OBJECTS)
DSPPGMREF PGM(APP/*ALL) OUTPUT(*OUTFILE) OUTFILE(AUDIT/PGMREF)
DSPFD FILE(APP/*ALL) TYPE(*ATR) OUTPUT(*OUTFILE) FILEATR(*PF *LF) OUTFILE(AUDIT/FILEATTR)
DSPFFD FILE(APP/*ALL) OUTPUT(*OUTFILE) OUTFILE(AUDIT/FIELDS)

These commands are a starting artifact, not a complete scanner. Run equivalent inventories across every relevant library, preserve qualified object names, and note where the commands require separate selections. Add source member lists, SQL catalog data, job schedules, queue configuration, authorities, triggers, constraints, journals, and integrated file system paths. A program reference outfile reports static references known to the program object, but dynamic calls and runtime overrides still need traces and CL analysis.

For each business transaction, store at least the entry point, calling identity, library list, programs and service programs reached, files and members opened, record formats used, messages sent, jobs submitted, spool files created, external endpoints called, and final business result. Attach confidence and evidence to each edge. A source reference, an object description, and a production trace do not carry the same weight. Conflicts between them are findings, not noise.

Dead code analysis must follow runtime evidence. Last change dates are weak signals on stable systems, and last used data may be absent or reset. A program untouched for fifteen years may run every night. Conversely, a recently compiled object may never receive a production call. Quarantine suspected dead paths, observe a representative business cycle, and get an owner to accept retirement.

The awkward question is how much discovery is enough. You have enough when the team can select a transaction and predict its reads, writes, outputs, failure signals, identity, and recovery path, then confirm that prediction in production evidence. Unknown dynamic edges should be explicit and tested. A large inventory with unexplained entry points is not complete.

Parity means matching outcomes and side effects

Handle million-line estates together
CodeHero processes systems over a million lines and reads every language in the tree in parallel.

Parity testing must compare observable business behavior, not whether translated functions return similar values. The old system's contract includes database mutations, ordering, messages, documents, queue submissions, external calls, locks, and failure recovery. A test that checks only the final row can approve a replacement that sent two invoices.

Build a harness around recorded, sanitized production transactions. Capture the input context needed to replay each case: user or role, library or company context, business date, relevant starting records, screen or request fields, and dependent responses. Run the old and new paths against isolated data, normalize irrelevant differences, and compare the full outcome. Keep the original trace immutable so a changing fixture cannot make both systems agree for the wrong reason.

A comparison record can stay simple:

{"case_id":"credit-release-017","result":"held","writes":[{"entity":"order","key":"48152","fields":{"status":"H"}}],"messages":["CREDIT LIMIT EXCEEDED"],"jobs_submitted":[],"documents":[]}

The identifiers are illustrative, but the shape forces the team to compare more than a status code. Add ordered reads when access order affects the result, timestamps with declared tolerances, and hashes for large document bodies. For generated reports, compare parsed fields and totals before worrying about byte identity. For financial extracts or fixed partner formats, byte identity may be the contract.

Include boundary and concurrency cases drawn from actual rules: blanks versus zero, maximum packed values, duplicate submissions, record locks, partial failures, cancelled screens, restart after a job ends, and two users editing one entity. Replay cases where operators chose an unusual function key or corrected a field after an error. These paths expose rules that happy-path samples miss.

Do not let the new implementation define the oracle. Product owners can approve intentional changes, but each difference needs a named decision and an expected result. Otherwise modernization becomes an excuse for silent semantic drift. The parity suite should survive cutover as the regression suite for later releases.

The target should expose the rules, not copy the machine

Keep regulated code inside
CodeHero can run supplied models air-gapped on hardware inside the customer's perimeter.

A sound target architecture preserves behavior while moving rules into components people can understand and operate. It does not recreate libraries, indicators, and file overrides with new names. Transliteration keeps the dependencies that made the old system hard to change.

Organize the target around business capabilities and transaction boundaries discovered from evidence. Go services can own transactional workflows and external APIs. TypeScript clients can express screen state without numeric indicators. PostgreSQL can enforce relational constraints and provide explicit schemas, views, and indexes. Rust makes sense for numeric kernels when measurement or domain requirements justify it, not as a default replacement for every calculation. These are design choices, not a mechanical language table.

Map each IBM i construct by meaning. A physical file may become a table, but multi-member use needs a deliberate model. A logical file may become an index plus a view or a query inside a service. A data queue may become a broker topic, a database outbox, or a direct call depending on its delivery contract. A data area may become configuration, durable state, or a sequence. A display file condition may become a client rule or a server authorization check. Put security decisions on the server even when the old screen helped enforce them.

Cut along seams that the behavior map can verify. A common sequence moves read-only inquiries first, then bounded updates, then batch and external interfaces, while a routing layer decides which implementation handles a transaction. The right sequence depends on coupling and operational risk. Avoid dual writes unless you can prove ordering, deduplication, and recovery; change data capture or a single authoritative writer is usually easier to reason about.

CodeHero reads the whole RPG and CL tree together, rewrites the system into Go, Rust, TypeScript, and PostgreSQL, and checks behavior with a parity harness against recorded production traffic. That approach is useful only if the input scope includes the files, display definitions, and operating paths described here; an agent cannot preserve an object nobody collected.

Keep modernization decisions separate from parity decisions. First state the old behavior with evidence. Then approve a changed rule, data model, or user flow and encode the new expectation. This separation lets engineering explain every difference at review and roll it back without guessing which deviations were accidental.

The exit test belongs to operations

The migration is finished when the business can operate, recover, audit, and change the replacement without depending on IBM i. Passing functional tests is necessary, but operators own the exit test because they handle the conditions that application demos avoid.

Prepare cutover as a controlled state transition. Define the last authoritative write on IBM i, the final capture or load, reconciliation queries, queue drain rules, document handling, credential changes, route switches, and a decision point for rollback. Name the person who can stop the cutover and the evidence they will use. If rollback would require merging writes from two systems, solve that design before the event rather than writing a hopeful runbook.

Reconcile business invariants, not just table counts. Orders by status, open balances, inventory by location, unposted batches, sequence continuity, pending messages, and document totals reveal different failures. Preserve source identifiers so the team can trace a target record back to its origin. Archive source definitions, object inventories, compiler listings where available, journals or extracts required by policy, and the parity evidence that supports acceptance.

Run recovery exercises before shutdown. Kill a worker during a multi-record update. Withhold an external response. Duplicate a message. Fill a queue. Restore a database backup and replay the missing work. Confirm that alerts reach a person with enough context to act. IBM i may have made some recovery behavior routine through journals, job logs, and queues; the replacement must make each responsibility explicit.

Security needs the same proof. Compare effective access by business role, including adopted authority and scheduled job identities. Remove temporary migration credentials, broad database grants, transfer accounts, and firewall exceptions. For regulated environments, document where data and models run, who can administer them, and what logs exist. Supporting a controlled environment does not create a certification.

Finally, leave the old system available in a defined read-only or recovery state only as long as an approved need exists. Assign an owner, cost, access policy, and destruction or archival condition. An indefinite fallback becomes a second production system that nobody tests. The honest finish line is the first ordinary operating day when a failed job, a disputed total, and a new rule can all be handled without calling the last person who remembers indicator 31.

FAQ

Is the AS/400 itself an ERP system?

No. The AS/400, now represented by IBM i on Power hardware, is a computing platform and operating environment. It may run an ERP package alongside custom RPG programs, database files, screens, batch jobs, and integrations.

What usually needs to move in an AS/400 migration?

Move or deliberately retire programs, database structures, display and printer files, CL routines, queues, schedules, security rules, integrations, reports, and operational procedures. Scope by business transaction so hidden dependencies appear.

Can Db2 for i physical files be copied straight to PostgreSQL tables?

The rows can often be extracted, but a straight copy misses members, packed and blank semantics, access paths, constraints, journaling, and rules enforced by writers. Map each file by observed use before choosing the PostgreSQL design.

What is the difference between a physical file and a logical file?

A physical file stores records. A logical file presents records from one or more physical files and may add keyed access, selection, field ordering, or joins, so it can act like a view, an index, or both.

Why do display files matter during RPG modernization?

Display files can control validation, protected fields, function keys, subfiles, messages, and visible state through indicators. Rewriting RPG without those DDS rules can weaken a business control while leaving the calculation intact.

How do you find hidden dependencies on IBM i?

Combine object and program reference inventories with CL analysis, file definitions, job configuration, authorities, and runtime traces. Static references alone miss dynamic calls, file overrides, library list resolution, and operator commands.

Should an IBM i rewrite preserve the green screen exactly?

Preserve the workflow decisions, validation, keyboard efficiency, and authorization boundaries, not every coordinate or color. A new client should make state clearer while the server enforces business and security rules.

How should teams test an AS/400 replacement?

Replay recorded and sanitized production cases against isolated old and new environments. Compare database changes, messages, jobs, documents, external calls, ordering, and recovery behavior, then approve every intentional difference explicitly.

Can an AS/400 migration use a phased cutover?

Yes, when transaction boundaries and data ownership are clear. Route bounded capabilities to the new system, keep one authoritative writer where possible, and prove synchronization and rollback before expanding the cut.

When is it safe to turn off IBM i?

Turn it off when operations can reconcile, recover, audit, secure, and change the replacement without relying on the old platform. Any retained read-only copy needs an owner, access policy, cost, and dated archival or destruction condition.