Use caseCustom software

Modernizing a 20-year-old Delphi or VB6 application with AI and tests, one module at a time

How I modernize a Delphi, VB6, Access or old PHP system: AI-assisted code reading, characterization tests from real data, and the strangler fig pattern.

A blueprint, not a client story. The business described is illustrative; the architecture, integrations and trade-offs are real, and this is how I would build it. By Ergini, .

The short version

An incremental modernization of a business-critical legacy application, such as a Delphi dispatch system or a VB6 or Access order system. An AI coding agent reads the old code and drafts the rules it enforces; characterization tests built from a year of real transactions pin that behavior; new modules then take over one at a time around the old system, run in parallel, and cut over only when their outputs match. The remaining developer and key users confirm every rule and sign off every intended change.

Best for
Companies whose core operations run on a 15- to 25-year-old application that one or two people still understand.
Connects to
The legacy application (Delphi, VB6, Access, old PHP or .NET Framework), SQL Server or Access database, Claude Code, The target stack, CI pipeline, Partner interfaces (EDIFACT, DATEV export)
The AI does
Reads the old code to document the rules it enforces, and drafts characterization tests and ports of each module for recorded data to check.
People do
The remaining developer and key users confirm what each rule means, approve every deliberate change in behavior, and choose when each module cuts over.
Built as
Custom Software Development, quoted per scope

The dispatch system only one person can change

Consider a road freight company in northern Germany with 90 trucks and a dispatch office of eight. Everything runs through a Delphi application written in 2004 by a developer who has since retired: order entry, tariffs and surcharges, truck and driver assignment, CMR consignment notes, invoices, EDIFACT messages to two large shippers, and a nightly export to the tax adviser's DATEV. It sits on a SQL Server instance nobody has dared to upgrade.

One developer remains who can change it, and even that developer knows only the tariff engine and the invoice module. The EDI unit has not been touched since 2011. Every change request waits in one queue, and every change is a risk, because there are no tests and the rules live wherever the original author put them that day. The office puts it plainly: it works as long as the one person who understands it is here.

Two dates make it urgent. From 1 January 2027, German companies with more than EUR 800,000 in prior-year turnover must issue e-invoices in an EN 16931 format such as XRechnung or ZUGFeRD, and this invoice module prints PDFs. The other date is the remaining developer's retirement, five years out and closer than it sounds.

The usual proposal is a rewrite: two years, a new system that does most of what the old one did, and a cutover weekend. It fails in a predictable way, because the old system's behavior is the specification and nobody has written it down. AI coding tools made the reading far cheaper; when Anthropic said in February 2026 that Claude Code could help modernize COBOL, IBM's shares fell about 13% in a day. They did not make proving cheaper, and proving is most of the work.

Where the rules hide in a Delphi application

Code archaeology means finding every place the system makes a decision. In an application this old they are spread across layers, and each layer needs its own way of digging.

Event handlers on formsPrice overrides, credit checks, status changes fired by a button clickThe coding agent lists every handler with its side effects: tables written, files created, messages sent
Dataset events such as BeforePostValidation, derived fields, silent defaultsThe same sweep, cross-checked against the screens that use each dataset
Triggers and stored proceduresStatus transitions, audit rows, invoice and order numberingScripted out of SQL Server and read next to the code that fires them
Report layouts (QuickReport, FastReport)Totals, rounding and wording computed at print time on CMRs and invoicesRendered for sample orders and compared field by field
INI files and lookup tablesPer-depot settings, printer routing, surcharge tablesCollected from every workstation and the server, then compared
The dispatchersWorkarounds, such as the field everyone uses for something elseInterviews, and a morning sitting next to them
The result is a rules register: each rule with its place in the code, a plain description, an example from real data, and the name of whoever confirmed it.

A rewrite plan and a strangler plan, side by side

Both end with the old application switched off. They differ in where the risk sits along the way.

Big-bang rewrite

  • Specify the new system from interviews and screenshots of the old one
  • Build for a year or more while the old system keeps changing underneath
  • Migrate all data and switch everything over one weekend
  • Find the undocumented rules through incidents after go-live
  • No way back once the old database stops taking orders

Module by module

  • Specify each module from recorded behavior: real inputs and what the old system produced
  • Move one module at a time while the old application runs the rest
  • Run each new module in parallel on live data until the outputs match
  • See differences in a daily report before any customer does
  • Switch a module back to the old code with one setting

One module's path from old code to cutover

Every module goes through the same sequence. The model reads and drafts; recorded data, comparison code and people decide whether the new code is right.

  1. 01 Trigger · Rules register

    Pick the next module

    Self-contained modules with clear inputs and outputs go first: e-invoice output, then EDI, then the tariff engine. The dispatch board, where people work all day, comes last.

  2. 02 AI model · Claude Code, read-only clone

    Dig into that module

    Claude Code reads the units, forms, triggers and report layouts involved and drafts each rule with file and line references, plus a query that shows the rule firing in real data.

  3. 03 Plain code · Copy of SQL Server, test harness

    Record the old behavior

    A harness replays a year of real, anonymized transactions through the old code path, or reads what it produced at the time, and stores inputs and outputs as the golden master: prices, invoice lines, EDI files, rows written.

  4. 04 AI model · Claude Code, GitHub Actions

    Draft the port

    The agent writes the module in the target stack with the characterization tests running on every change. Recorded outputs sit behind a CODEOWNERS rule, so the agent cannot quietly redefine what correct means.

  5. 05 Decision

    Does the replay match?

    The comparison is plain code, to the cent and the byte.

    • Every recorded case matches then on to the parallel run
    • Differences trace to a porting mistake, such as rounding or dates then the port is fixed and the recording stays
    • The old behavior looks like a bug then a person decides whether to keep it or change it on purpose
  6. 06 Person

    Sign off deliberate changes

    The remaining developer or the dispatch lead approves each intended change in behavior, with the affected cases listed. Nothing changes silently.

  7. 07 System · Both systems, shared SQL Server

    Parallel run on live data

    The new module processes every real transaction alongside the old one through at least one month-end, and a daily report lists any difference. The old output is still the one that counts.

  8. 08 Result · Feature flag

    Cutover with a way back

    A setting switches the module to the new code. The old path stays available through another month-end; then its code and tables are retired.

The model drafts in two steps. Whether a result is right is settled by recorded data, the comparison code and a named person.

A replay run that caught two problems

The tariff module's second draft, compared against a year of orders priced by the old system.

Replay, tariff module, draft 2

  1. replay(module: "tariff", source: "orders 2025", cases: 48112)47,961 identical / 151 differ / 0 errors
  2. group_diffs(module: "tariff")all 151 differ by 0.01 EUR on the fuel surcharge, each at an exact half cent
  3. explain(case: "A-2025-031877")surcharge 18.125 / old: Delphi Round() sends halves to even, 18.12 / new: rounds half up, 18.13
  4. Port changed to round half to even. Rule R-114 added to the register.
  5. replay(module: "tariff", source: "orders 2025", cases: 48112)48,109 identical / 3 differ
  6. explain(cases: "remaining 3")all 3 carry a negative weight / old code prices them as returns to the Bremen depot / no rule documents this
  7. Remaining developer · Review comment

    Those are depot returns. Dispatchers type a minus weight because the returns screen never worked. Keep it for now, and put a proper returns flag on the list.

  8. Kept on purpose as rule R-115, signed off by the developer.
The agent made the rounding mistake a careful person could make, and the recording caught it. The one case that needed judgment went to the person who had it.

Six ways a migration can hurt the business

None of these show up in a demo of the new system. Each has a specific guard.

Rules nobody wrote down

A surcharge for one customer on Saturdays, hard-coded in a button handler in 2009. Archaeology finds it, the replay proves it still fires, and the register records who confirmed it is still wanted. A rule nobody can explain is kept until someone can.

Twenty years of data quirks

Names typed with Polish characters but stored in a Western European code page, status codes whose meaning changed in 2014, three spellings of the same shipper. The migration maps encodings explicitly, carries old codes with their date ranges, and reconciles row counts and totals per table on every load.

Users who rely on bugs

The negative weight for depot returns is typical. Each workaround is kept, turned into a proper feature, or removed with the users' agreement and a short training, and the decision is written down. Removing one silently is how a new system gets called worse than the old one.

Partners who will not change

Two shippers exchange EDIFACT messages and the tax adviser imports the DATEV export; neither will adapt to your project. New modules are tested against partner files byte for byte, ignoring only timestamps and control numbers, and each partner gets a test exchange before cutover.

An AI refactor that changes behavior

Coding agents tidy as they go: a rounding call swapped for the idiomatic one, a date comparison made 'correct', an empty exception handler turned into an error. Each is reasonable and each changes results. The characterization tests catch it on the next run, which is why they exist before the agent touches anything.

Screens that need the works council

A new dispatch board may show driver locations or hours more visibly than the old one. In Germany, systems able to monitor employees need the works council's agreement, so those screens go to it early instead of being discovered at rollout.

Cutover order for a dispatch system

Modules move in an order that starts where risk is lowest and the deadline nearest. Each one earns the next.

E-invoice outputSelf-contained, legally due, and built on data the old system already writesA month of invoices passes the KoSIT validator and matches the old PDFs' totals
EDI with the two shippersFixed formats, and the partners act as the testBoth partners accept a test exchange and a month of parallel files matches
Tariff engineDense rules, but numbers in and numbers outA year of replayed orders matches to the cent and a live month-end ran in parallel
DATEV export and reportsRead-only, so low risk once the data is rightThe tax adviser imports a parallel month without corrections
Order entryPeople type into it all day, so it follows the rules it depends onDispatchers have used it in parallel and signed it off
Dispatch board, then retirementLast, because it touches everythingNo transaction has used old code for a month, and the old database is archived read-only
The e-invoice module goes first because of the deadline, and it has its own blueprint: issuing XRechnung and ZUGFeRD from a legacy system.

Replace it with a product, or modernize what you have?

If the process is standard, buy. Road freight companies have mature transport management systems such as Soloplan CarLo or Winsped, forwarders run CargoWise, and manufacturers and wholesalers have ERPs that cover orders, invoicing and EDI out of the box. If most of what your old application does is what those products do, the honest recommendation is to move the data and the people to one of them, and I will make it.

Modernizing your own code wins when the software encodes how you make money: tariffs no product models, customer-specific rules, interfaces a vendor will not build. Price the middle paths too. If the code is sound and the developer likes Delphi, upgrading to a current Delphi release and replacing the database layer can be the cheapest modernization of all. For .NET Framework, AWS Transform and similar upgrade tools handle much of the mechanical port, as commercial converters do for VB6. None of them can tell you whether the behavior survived, so the characterization tests are needed on every path.

If what you need is automation around the old system rather than a replacement, such as keying data into screens that have no API, that is a different and smaller job: automating a legacy system that has no API. How I choose between coding agents for this kind of work is in Claude Code vs Cursor.

How you would know it is working

A blueprint has no results to report, so here is what I would measure from the first week instead, on your own data.

Replay match rate
Per module, the share of recorded historical cases where new output equals old output, with every remaining difference explained and signed off.
Parallel-run differences
Differences per day between old and new outputs on live data, which should reach zero and stay there before cutover.
Confirmed rules
Rules in the register confirmed by the developer or a key user, against those still open.
Share of work on new code
Orders, invoices and messages handled by new modules as a share of the total: the plain measure of progress.

What a build like this costs

This is built as Custom Software Development, which is quoted per scope after a free 30-minute call rather than from a fixed table, because the range of what counts as custom software is too wide for one honest number.

What it costs to run

During the migration you pay for both systems, because the old server and licenses stay until the last module moves. Coding agent usage is small next to engineering time. After cutover nothing in the runtime depends on a model; the new modules run on ordinary hosting.

What moves the price

  • Size and state of the codebase: how many forms and units, and how much logic sits in event handlers, triggers and reports
  • How many interfaces must keep working unchanged: EDI partners, accounting exports, telematics, customs
  • Data volume and quality: encodings, duplicates and twenty years of status codes
  • How long each module must run in parallel before the business trusts it
  • How much time the remaining developer and key users can give to interviews and sign-offs

Who this is for

  • Road freight companies, forwarders and wholesalers whose dispatch, pricing or invoicing runs on a Delphi or VB6 application from the early 2000s
  • Manufacturers with an Access or old PHP system for production orders that only one person can change
  • German companies that need XRechnung or ZUGFeRD output by 2027 from a billing module nobody dares touch
  • Teams that started a full rewrite once and stopped halfway

Questions people ask about this

Can AI migrate legacy code automatically?

Not safely on its own. A coding agent can translate a Delphi or VB6 unit into C# or TypeScript in minutes, and the result often looks right. What it cannot know is which quirks the business depends on, such as how a surcharge rounds. So the tests come first: a year of recorded inputs and outputs decides whether a port is right, and every pull request gets an automated AI review pass before a person reads it.

What is the strangler fig pattern?

A way to replace a system gradually instead of all at once. New code takes over one piece at a time, such as invoicing, then EDI, then pricing, while the old application keeps running the rest. The name comes from a fig that grows around a host tree until it stands on its own. Each piece runs in parallel before it takes over, so a mistake affects one module, not the business.

Our system is too old for an API. Can it still be modernized?

Yes. Modernizing works from the code and the database, not from an API, so age matters less than people expect. If the source is lost or no longer builds, the database, the printed documents and the screens still show what the system does, and new modules can take over from there. Old compilers and missing documentation slow the archaeology down; they do not stop it.

How long does a Delphi or VB6 migration take?

Longer than a vendor demo suggests, and far shorter than it feels from inside a stalled rewrite. The first self-contained module can run in parallel within weeks of starting. The whole system takes as long as its modules times the parallel run each needs, so it is planned and priced one module at a time as custom software, after a free call to size the codebase.

Is it safe to let an AI tool read our source code?

It can be, with the right setup. Source code is rarely personal data; database extracts are, so the agent works on anonymized copies and never touches the live database. If code must stay under your own contracts, Claude Code can run on Anthropic models through your own AWS Bedrock or Google Vertex AI account in an EU region. Nothing the agent writes reaches production without tests and review.

Sources