Modernizing a Licensed Software Product

ModernLift · ·12 min read
Part 3 of 8

You modernize a licensed software product slice by slice, on the shipping product, never as a stop-everything rewrite. You carve the codebase into slices along natural seams, pick the slice that returns the most roadmap or removes the most risk, build the modern replacement behind a stable interface, prove it behaves identically to the old code against real inputs and across the environments customers run, ship it inside an ordinary release, and only then remove the old code. Each slice is independent, validated against the field's version-and-environment spread, and reversible — so the product keeps working in every customer install throughout, the release train never stops, and you are never one big-bang cutover away from breaking software that customers depend on. The method trades a rewrite's single distant payoff for a stream of small, proven, low-risk releases, which is exactly the trade that makes it safe to do on a product you cannot afford to break in the field.

Part 2 showed that the architectural change most installed products need — a real service tier — can be introduced incrementally rather than rewritten. This part is the method that makes that, and every other modernization, work on a product you ship to customers. It is not a secret technology. It is a loop, applied with discipline, on the live product. The SaaS series lays out the same loop for a product the vendor runs; here we adapt it for the harder case, where the product runs in environments you don’t control.

Why not just rewrite it

It is worth restating the option this method exists to avoid, because the instinct is strong. When a product’s codebase feels exhausted, the seductive move is to declare it bankrupt and rebuild it clean. For a licensed product this is close to the worst available choice. A rewrite freezes the release train for its duration — breaking the implicit deal of the maintenance contract — asks the company to wager its customer base on a project that delivers nothing until the end, and carries the failure rate that makes it the riskiest thing in software: BCG found in September 2023 that up to 70% of digital transformations fail to deliver on their objectives. And it is uniquely dangerous for installed software, because the rewrite has to be cut over in customer environments you cannot see, where the bugs you missed surface as incidents on their site, not yours. The slice-by-slice method is the alternative that gets you a modern product without that bet.

The method, end to end

Slice-by-slice modernization is a loop you run many times, each pass modernizing one piece of the product while the rest keeps running and shipping. The pattern underneath is the strangler fig: the modern product grows up inside the legacy one, taking over a slice at a time, until the old code can be removed. Six moves make up each pass.

1. Carve the product into slices. A slice is a coherent piece that can be modernized and shipped on its own — a module, a workflow, a data domain, the service-tier extraction from Part 2. The art is in the seams: good slices follow the boundaries already latent in the codebase, where the coupling to the rest is thin enough to seal behind a clean interface. Slice along real seams and each piece comes away cleanly; slice arbitrarily and the seams leak, and you spend your effort fighting couplings you created.

2. Choose the slice that returns the most. You do not modernize in arbitrary order. You pick the slice where modernization returns the most roadmap or removes the most risk — the module the team keeps colliding with, the architectural ceiling blocking a web front end, the dependency closest to end-of-life. Each pass should pay for itself in velocity recovered, risk retired, or support cost removed, not just in code that is newer.

3. Recover what the old code actually does. Before you can replace a slice you have to know its true behavior — including the undocumented rules buried in the old client and stored procedures, the special cases added for one important customer years ago, the quirks the field now depends on. This tribal knowledge is the asset most at risk, and recovering it from the code is the step a rewrite skips and regrets. The replacement has to match what the old code does, not what anyone remembers it doing.

4. Build the replacement behind a stable interface. The modern slice is built behind an interface the rest of the product already talks to, so the surrounding code neither knows nor cares that the implementation behind it has changed. The seam is what lets old and new coexist — the rest of the application keeps calling the same boundary while the thing behind it is swapped out. For a client-server product this is often where the anti-corruption layer sits, keeping the new slice clean against the legacy data it must still interoperate with.

5. Prove parity — across the environment spread. This is the move that makes the method safe, and it carries a dimension internal modernization does not. Before the modern slice reaches a customer, you prove it behaves identically to the slice it replaces — same outputs, same behavior, edge cases included — by exercising it against real inputs and reconciling any divergence. But “the field” is not one environment: the slice has to match across the operating systems, database versions, and prior releases customers actually run. Parity validation is what turns “we think it’s equivalent” into “we have shown it is, everywhere it has to run” — and it matters more here than anywhere, because once a regression ships into a customer’s environment you cannot quietly roll it back the way a SaaS team can. On a product customers depend on, parity is non-negotiable.

6. Release, then remove. Once parity holds, the slice ships inside an ordinary release — indistinguishable to the customer from any other update. Customers install it on their own schedules, so old and new releases coexist in the field for a while, with the old code path retained as a fallback until the modern slice has carried real work cleanly across the base. Then the old code is removed, the slice is done, the product never broke in any environment, and you return to step two for the next one.

What the method buys you

The reason to work this way is not aesthetic. It buys four things a rewrite cannot.

The release train never stops. Because only one slice is in flight at a time and the rest of the product is untouched, the team keeps shipping fixes and features in parallel. Modernization travels out as ordinary releases, honoring the maintenance contract rather than suspending it.

Risk stays bounded. The blast radius of any release is one validated, reversible slice — not the whole product cut over at once in environments you can’t see. You are never one big-bang away from breaking installs across the field.

Value arrives continuously. Each finished slice delivers its benefit — recovered velocity, a new front end made possible, support cost removed — on its own. If priorities shift, you stop with every completed slice already banked and shipping.

The estimate gets better as you go. The first slices teach you the real shape of the product and the real behavior of the field, so each subsequent pass is estimated against evidence rather than a guess. The largest unknown in any modernization — what it will actually take — shrinks with every slice instead of looming over the whole thing.

Where AI fits, and where it does not

Modern tooling, including AI, genuinely accelerates parts of this loop — reading and mapping an unfamiliar legacy slice, recovering the undocumented behavior buried in old client code and stored procedures, drafting the modern replacement, generating the comparisons parity validation runs on. This is how the work gets delivered faster; it is not a product you buy and it is not autonomous. Every AI-assisted step sits under human review, and the parity gate is exactly what catches the cases where the machine got it subtly wrong. AI shortens the loop; it does not remove the engineering judgment or the validation that make it safe. We treat this honestly in the AI in modernization series — useful where it is useful, never a substitute for proof.

What this discipline costs, and when it isn’t worth it

Slice-by-slice is the right default for a shipping product, but it is not free. It carries overhead a rewrite does not: maintaining the seams, validating each slice across the environment spread, keeping the product releasable throughout. And it depends entirely on the quality of the slicing and the rigor of the parity gate — done carelessly, with arbitrary slices and parity skipped to hit a date, it inherits the rewrite’s risk while keeping its own overhead. For a tiny product you could rebuild and re-deploy in weeks the discipline may be more than the situation warrants. The method earns its keep when the product is too widely installed or too load-bearing to risk a big-bang. It is a discipline, not a guarantee.

Where this leads

This part has laid out the core method for a shipping product: carve, choose, recover, build behind a seam, prove parity across the field, release, remove — looped until the product is modern, with the release train running throughout. The next several parts apply it to the specific transitions ISVs most often need. Part 4, Desktop-to-Web Application Migration, takes the most common one — moving the thick desktop client to a web front end — and shows how the method turns a daunting rewrite into a sequence of ordinary releases.

Frequently asked questions

How do you modernize a software product without stopping releases?
By modernizing in slices behind stable interfaces instead of rewriting the whole product. You isolate one slice of the codebase, build its modern replacement, prove the replacement behaves identically to the original across the environments customers run, and ship it inside a normal release — while the rest of the product is untouched and the team keeps delivering fixes and features elsewhere. Because each slice is independent and reversible, modernization travels out as ordinary updates rather than as a release that freezes everything, so the product keeps shipping the entire time.
What makes modernizing a shipping product different from internal modernization?
You don't control where it runs. A slice that passes every test on your machines still has to behave correctly in customer environments you have never seen — different operating systems, database versions, hardware, and prior product releases. So parity validation for a product has a second dimension: the new slice must match the old not just functionally but across the field's environment-and-version spread, and it must ship as a release that installs cleanly over whatever the customer is already running. Getting a slice right internally is necessary but not sufficient; it has to be right in the field.
What is parity validation and why does it matter more for installed software?
Parity validation is proving a modernized slice produces the same outputs and behavior as the code it replaces — edge cases included — before it carries real work, by exercising it against real inputs and reconciling any divergence. It matters more for installed software because once a release is sitting in a customer's environment you cannot quietly roll it back the way a SaaS team can; a regression becomes a support incident on the customer's site. Parity is what lets you ship a modernized slice into environments you don't control with confidence that it matches what customers already trust, rather than hoping it does.
All 8 parts of Non-SaaS Product & License Modernization →