The system that still works and nobody wants to touch. We read the code first.
A legacy system rarely fails outright, it just becomes impossible to change safely: the developer who understood it left, the framework is off the vendor's support list, and every fix is made blind because nothing would catch a regression.
What this is
The systems that end up here share a shape: an aging stack, sometimes a framework version the vendor stopped patching years ago, sometimes a build that only works on the one machine still configured for it. Business logic lives in stored procedures, in a cron job nobody remembers writing, or in one developer's head, and there are no tests, so every change is a bet rather than a fact. None of that means the system is bad. Plenty of these run a real business every day. It means changing it is the risky part, not running it.
The method starts with reading, not rewriting. We run the current system, read the code and talk to whoever still uses it, and write down what it actually does, including the rules that were never documented because whoever wrote them assumed they were obvious at the time. Then we write characterization tests that lock in that current behavior, so "the new version still does what the old one did" is something we can prove rather than something we hope a QA pass catches. Only after that does the replacement start, usually with the strangler fig pattern: the new system built alongside the old one, traffic moved over piece by piece, with a working rollback at every stage instead of one cutover night that either works or does not.
The actual risk in this work is not the obvious 90% of the system, it is the rule that only fires under one specific condition: a discount override written into the code for a contract that expired years ago and never got removed, a rounding rule that exists because of some long-ago tax audit finding. A rewrite done from the spec alone reproduces the visible behavior and drops rules like that, and they surface after go-live, when the one customer who depended on it calls, not before. Reading the system first is what catches them while they are still cheap to fix.
What you get
A written account of what the system actually does
Including the undocumented rules found by reading and running it, not assumed from a spec that was never accurate.
Characterization tests locking in current behavior
Written before anything changes, so a regression is caught before a customer finds it.
A migration path with a rollback point at each stage
Strangler fig or a phased cutover, chosen against whether the system can afford downtime, not a single big-bang release.
The modernized system
Current framework, supported dependencies, a build that runs on more than one machine.
Tests that make future changes safe
Which the legacy version usually did not have, so the next change is not also a gamble.
Data migration where the underlying storage changes too
Coordinated with the code migration; see data migration for that work on its own.
Documentation of what turned out to be surprising
So the next person does not rediscover the discount override the hard way.
Source, build pipeline and deployment, handed over
Reproducible without us.
When this fits, and when it does not
A good fit
- The system works, but nobody currently on staff can safely change it, and every fix is a gamble.
- The stack is off the vendor's support list, or the one machine that can build it is a single point of failure in itself.
- Business rules exist only in the code or in one person's head, and that person is a departure risk.
- You need a feature or an integration the current architecture cannot support without a rewrite of something underneath it.
- A compliance requirement or a customer contract calls for a supported, patchable stack, and the current one is not that.
Not a good fit
- The system works fine on its own, and the only real problem is that nothing else can talk to it. That is API integration, usually cheaper, and worth trying before a rewrite.
- The application logic is fine and the only thing that has to change is where the data lives. That is data migration on its own.
- You want a rewrite because the code is old, not because it is causing a real problem. Old and working is not broken, and the budget is better spent where something is actually costing you.
- Nobody can currently run the system at all: the source is lost, the environment is dead, and there is nothing left to read. That is closer to a fresh build than a modernization, and we will say so rather than bill you to reverse-engineer a ghost.
How it runs
- 01
Read, run and talk to whoever still uses it
Before proposing what changes, so the plan is grounded in what the system actually does.
- 02
Write characterization tests
Against current behavior, so "it still does what it did" is provable rather than assumed.
- 03
Choose the migration path
Strangler fig for a system that cannot go down, a phased cutover where a maintenance window is acceptable.
- 04
Build and move traffic piece by piece
With a rollback point at each stage, rather than one cutover that either works or does not.
- 05
Hand over the modernized system
Its tests, and documentation of what turned out to matter.
Questions we get
Do you always rewrite from scratch?
No, and rewriting is usually the wrong first move. Most of these projects need a handful of specific things fixed and a build that reproduces reliably. A full rewrite is the last resort, taken when the architecture itself cannot carry the change you need, not the default.
What if there is no documentation at all?
That is normal, not a blocker. We read the code, run it, and write down what it actually does as we go. The absence of documentation is the reason this work exists, not a reason it cannot start.
Can the new system run alongside the old one during the change?
Usually yes, that is the strangler fig pattern: the replacement built alongside the original, with traffic moved over incrementally rather than a single cutover, so there is a working rollback at every stage.
Will you touch our data during this?
Only if the underlying storage is changing as part of the modernization. If it is, it is planned and executed as its own step; see data migration for how that work is scoped when it stands on its own.
Got a system nobody wants to touch?
Tell us what it does and why changing it feels risky. An engineer reads the code before proposing anything, and the first thing you get back is what would break if it were touched.