Migrating a Clipper/xBase app without losing 30 years of business rules

Every failed xBase migration I have seen died the same death. Not from bad technology choices — from a quiet assumption: “we know what the old system does.”

Nobody knows what a 30-year-old Clipper application does. Not the company that owns it, not the operators who use it eight hours a day, and not the developer who wrote it, even when he is still around. The application is the specification. The PRG files are where the truth lives, and a third of that truth was never intended: rounding artifacts that customers’ invoices have depended on since 1996, an index collation quirk that determines which duplicate wins, a date-window trick from the Y2K panic that still silently reclassifies years.

If you rewrite from a requirements document, you are rewriting from fiction. Here is the methodology that works instead.

Step 0: get the old system somewhere you can run it on demand

You cannot test against a system you are afraid to touch. For DOS-era Clipper this often means a containment step first: port the code to Harbour — the open-source Clipper-compatible compiler, which shipped a fresh stable release in July 2026 — so the same PRG code runs as a native Windows executable against the same DBF/NTX/CDX files. We have done this with production utilities: most code recompiles; the work concentrates in screen handling, printing and the occasional inline C or assembler.

The port pays for itself twice. It ends the 16-bit hardware emergency immediately, and it turns the legacy system into something you can drive from scripts — which is everything for what comes next.

Step 1: build the parity harness before the new system

A parity harness is three parts:

  1. Frozen inputs. A copy of production data at a known point, plus a library of scripted transactions: post an order, receive goods, close a month. In xBase systems the “API” is often the database itself, which is convenient — writing DBF rows is driving the app.
  2. Captured outputs. Everything the business actually consumes: report files, printed invoices redirected to text, month-end totals, the resulting state of every table. Text-mode legacy output is a gift here — it diffs perfectly.
  3. A differ with a memory. Something that runs both systems on the same inputs and produces a human-readable list of differences — and remembers which differences were reviewed and accepted, so the signal never drowns in known noise.

The critical rule: capture behavior, not intentions. If the old system computes 12% of 1850 as 221.99 because of how Clipper’s numeric type rounds through an intermediate, then 221.99 is the correct answer. Your customers have 30 years of paper that says so. You can fix the bug later — deliberately, with sign-off — but on migration day, faithful means identical.

Step 2: mine the places where rules hide

Business logic in xBase systems does not live only in the obvious calculation routines. The parity harness must cover the habitats:

  • Index expressions. UPPER(SUBSTR(CUSTID,1,3)) + DTOS(DUEDATE) is not plumbing — it is the rule for how work is sequenced. Conditional indexes (FOR !PAID) are business filters.
  • Collation. Index order under a DOS codepage differs from ANSI order for every character beyond ASCII. Reports “sorted by name” can legitimately differ between old and new — decide which truth wins before users find out.
  • Deleted records. SET DELETED OFF code paths see flagged-deleted rows. Some reports in the wild depend on that accidentally, and have for decades.
  • Empty vs zero. xBase distinguishes an empty numeric field from 0 in ways that SQL NULL maps onto imperfectly. Aggregates over “empty” columns are a classic parity breaker.
  • Date arithmetic. Two-digit-year windows, DATE()+30 month-end assumptions, 1900/2000 pivots patched during Y2K by someone in a hurry.
  • The codepage layer. If the data is cp862 Hebrew or cp866 Cyrillic (usually with no codepage byte set in the DBF header — DOS apps rarely recorded one), then every comparison, sort and print step embeds an encoding assumption. Get it wrong and parity fails everywhere at once, uninformatively.

The assessment phase is largely about finding these habitats in your codebase and making sure the harness exercises them.

Step 3: migrate in slices, gate each slice on the diff

With the harness in place, the migration itself becomes almost boring — which is the goal:

  1. Move the data to SQL with its own verification (row counts, per-column checksums, codepage round-trips). Keep the pipeline repeatable; run it nightly.
  2. Rebuild one module at a time on the new stack, starting with read-only reports.
  3. Run both systems on the frozen inputs. Diff. Fix. Repeat until the diff is empty or every remaining line has a written, signed-off explanation.
  4. Cut that module over. The legacy system remains the system of record for everything else.

“Done” stops being a feeling and becomes a number: zero unexplained differences across N scenarios covering M% of last year’s transaction volume. Management can see the number move weekly. When someone asks “is the new system ready?”, the answer is a report, not a promise.

What this costs — and what it saves

Building the harness is typically 15–25% of the migration budget, spent before any new-system code exists. Teams under time pressure are tempted to skip it. What they buy instead is the expensive kind of testing: production users discovering differences one angry phone call at a time, for a year.

The harness also outlives the migration. It becomes the regression suite for the new system — the first automated tests this application has had in its entire multi-decade life.

The uncomfortable summary

  • The old system is the only complete specification of itself. Treat it as the oracle.
  • Preserve bugs first, fix them second — separately, visibly, with sign-off.
  • Contain the platform emergency (Harbour) so you can migrate calmly instead of heroically.
  • Let a diff decide when you are done. Opinions, including yours and mine, do not count.

This is the methodology behind our Clipper/xBase migration service and, with different tooling, our FoxPro work. If you want the short version applied to your system: that is what the assessment is for.

Written from real project experience; details anonymized. Questions about your own system? Get in touch — first look is free.

Start with a fixed-price Legacy Assessment

One to two weeks. You get a full inventory of the application and its data, a risk map (what breaks first, what is undocumented, what nobody can rebuild), a data-quality report and a migration roadmap with honest effort estimates — a document you can execute with us or with any other team. Fixed price, agreed before we start.

Book an assessment Undecided? Rewrite vs convert vs wrap