Why replacing a legacy system is unlike building something new: recovering requirements nobody wrote down, preserving the regulatory behaviour buried in old code, and why data migration is always underestimated.
Legacy replacement fails in its own particular way. It rarely fails on technical grounds. It fails because the requirements are missing, incomplete or discovered too late. The requirements were never written down. The developers who built the system left years ago. Users can tell you what they need it to do, but not how it currently works. Business rules sit embedded in undocumented code, and regulatory obligations exist only in the heads of long-serving staff who may leave before you have captured what they know. Add an offshore team (distant, working across time zones, with limited access to users and no knowledge of the system they are replacing) and you have the hardest requirements problem in this book.
What makes legacy replacement different
When you build something new, you gather requirements for what users want. When you replace a legacy system, you are doing the opposite first: you are documenting what already exists, much of it undocumented, before you can specify what the replacement must do. That inversion is the whole difficulty, and most of the techniques in this chapter exist to manage it.
You are documenting the undocumented. Legacy systems rarely carry comprehensive documentation. What exists is usually outdated, incomplete or wrong. The system evolved over years through countless changes, each one solving an immediate problem without anyone updating the documentation. Eventually the code became the documentation, except nobody can read it. You’ll find systems where the only documentation is a 200-page specification from a build fifteen years ago — so far out of date it is worse than useless. It is misleading.
Users can’t articulate how it works. Ask users what they need and they will tell you: ‘make it work like the current system’. Ask them how the current system works and they will say: ‘it just works’. Users know what they do with the system. They don’t know what the system does for them. They never see the calculations, the validations, the background processes, the edge-case handling, because the system handles all of it, silently, until you ask them to document it. Then you discover they have no idea.
Business rules are buried in code. Complex business rules accumulate over time, embedded in application code, database triggers, stored procedures and configuration files. Nobody documented them. The developers who wrote them moved on. The business users who requested them retired. The rules still execute and the system still enforces them, but nobody remembers why. Business rules turn up in the most unexpected places: hard-coded values in SQL scripts, calculations in Excel spreadsheets users run manually, validation logic sitting in UI code that should have been in the business layer. Finding these rules is detective work: code reviews, database analysis, testing the current system to understand its behaviour, and interviewing everyone who has used the system long enough to remember the exceptions.
The broad principles of discovery still apply here, and Chapter 2 covers them: time with users, watching behaviour rather than collecting stated wants. Legacy replacement specialises those principles for a system that cannot explain itself.
Read more: Gathering Requirements
Discovering requirements when no documentation exists
Discovery is where legacy replacement is won or lost, and no single technique captures everything. You need several, run in parallel, each catching what the others miss.
Reverse-engineer the current functionality. Start with what the system produces: reports, calculations, data exports, screens. Work backwards from there. What inputs create these outputs? What calculations are applied? What rules are enforced, what validations occur? This reveals functionality nobody documented and few people understand. Reverse-engineering a single monthly report can surface a critical reconciliation process whose logic is complex, undocumented and, as it turns out, incorrect: wrong for years, with users having quietly learned to adjust for it. The replacement then has to fix the calculation while keeping backward compatibility for historical data — a requirement nobody would ever have stated, because nobody knew it existed.
Shadow users. Watch users work with the current system. Don’t ask them to explain it; watch what they actually do. You will find manual steps that supplement the system, workarounds for its limitations, data they export and massage in Excel, checks they perform by hand, and processes that ought to be automated but aren’t. Users rarely mention any of this, because they don’t think of it as ‘system functionality’. To them it is just ’things I do’.
Test the current system. The only way to fully understand how a system behaves is to exercise it. Build test scenarios that cover normal processing, edge cases, error conditions, boundary values, different permission levels and different data combinations, and document what the system does in each. That record becomes your acceptance criteria for the replacement: the behaviour you are committing to reproduce. Use cases are well suited to capturing current behaviour this way, step by step, because they describe what the system does rather than what someone wants; Chapter 3 covers how to write them.
Extract business rules from the code. When rules aren’t documented, get them out of the code. This takes technical skill: reading code, understanding database schemas, working through stored procedures, interpreting configuration files. Not everyone gathering requirements can do this, but for legacy replacement in a technical environment it is essential. The practical approach is to work alongside developers, reviewing critical sections of code together and asking questions until the business logic is clear enough to write down in plain language.
Edge-case archaeology. Edge cases are the killer. The system handles them correctly and nobody remembers they exist until they break in the replacement. Month-end processing that only runs once a month. Year-end calculations that run once a year. Leap-year handling, daylight-saving adjustments, public-holiday processing. The current system does all of it; the code runs; it works. Users never think about it because it is automatic — until it isn’t. Finding these cases means testing the current system systematically across different date ranges and data conditions, and reading the incident logs for problems that have surfaced before.
Two more sources are worth chasing early, because they expire. Original developers are gone, but the current support team knows how to fix common failures; they can tell you which buttons to press when things break, even if not why the system works the way it does. Tribal knowledge sits in users’ heads — the workarounds, the once-a-year exceptions, the manual processes that prop up the automated ones — passed verbally from experienced staff to new starters, or never passed at all. When those employees leave, the knowledge leaves with them, so prioritise interviews with anyone approaching retirement. Their knowledge is invaluable and time-limited.
Preserving regulatory functionality
Some of the strongest examples of this whole problem come from regulated environments such as financial services, and they make a useful illustration even if your own system is nothing of the sort. In a regulated setting, compliance requirements change over time, but the system must retain historical functionality for auditing and reporting. The replacement has to preserve that functionality exactly: calculations must produce identical results, audit trails must be maintained, historical data must stay accessible, regulatory reports must match. Miss one such requirement and the consequence is an audit failure, a penalty, or worse.
The trouble is that these requirements are rarely documented in one place. They are scattered across emails, audit reports, regulatory guidance and institutional memory, and some exist only in the code — which is why this is its own strand of discovery, distinct from the functional reverse-engineering above. Regulatory requirements accumulate through legislative changes, guidance updates, audit findings and the evolution of industry standards, and the historical ones remain in force even as new ones are added. Finding them means reviewing audit reports, interviewing compliance staff, analysing the regulatory reports the system already produces, checking its calculations against the published formulas, and reading the correspondence with regulators. It is tedious work. It is also the kind that, left undone, surfaces only after go-live.
The documentation set
Legacy replacement means documenting several things at once: what exists now, what should exist next, and how to get from one to the other. It helps to keep these as distinct documents, because they answer different questions for different readers.
Current-state documentation records how the system works today: the process flows showing what users do and what the system does, including the manual steps and workarounds; the business rules it enforces, with worked examples; the key data entities and how records are created, updated and deleted; the integration points; the compliance features and audit trails; and the known problems that should not be carried forward. This serves two purposes at once. It helps developers understand what they are replacing, and it becomes your reference for what the replacement must preserve.
Future-state requirements record what the replacement must do: the functional requirements organised by user role or business process, the non-functional requirements (performance, security, availability, scalability and the other quality attributes), the integration and reporting requirements, and how long data must be retained and in what form. The single most important discipline here is to mark every item as either ‘must preserve from the current system’ or ’new requirement for the replacement’. The replacement is an opportunity to improve, but only if you are clear about what is allowed to change and what is not.
Migration requirements record how to get from old to new: what data must be migrated and how it should be transformed and validated; the cutover plan with its timing, rollback procedures and contingencies; whether the two systems will run in parallel, for how long, and how differences are reconciled; how users will reach historical data after cutover; and the training and communications that go-live needs. Treat this set as first-class. Migration often accounts for more effort than building the replacement itself.
Integration requirements document every point where data enters or leaves: the systems that send data in and those that receive it, what is exchanged and how often and in what format, whether each integration is real-time or batch, what happens when one fails and how it recovers, and how it can all be tested without touching production. Integration failures are a leading cause of go-live delays, so document them thoroughly.
Where a regulated system is involved, a compliance set sits alongside these: the regulatory calculations with references to the relevant guidance, the audit trails (what user actions and system events are logged, and for how long), the required reports with their format, frequency and recipients, the accuracy tolerances and rounding rules, and the retention periods. Write this assuming auditors will read it, because they will.
Read more: Functional vs Non-Functional Requirements
Why data migration is the most underestimated effort
If one part of legacy replacement is routinely underestimated, it is data migration. The reason is that ‘build it like the current system’ makes the new system sound like the hard part. It isn’t. Legacy data is messy: incomplete, inconsistent, duplicated and incorrectly formatted. It violates rules the current system never enforced, and it contains special cases nobody documented. Cleaning it takes time. Validating the migration takes more. Handling the exceptions takes more again. A sound rule of thumb is to budget at least as much effort for migrating the data as for building the replacement, and often more.
This is also where a delivery team can look like it is failing when it isn’t. The demos slip, the release dates move, the bug count climbs; from the outside it reads as a team that can’t deliver, when in fact it is a team being defeated by data nobody scoped. The fix is to make migration explicit and resourced from the start, not discovered in the final sprints.
Why offshore teams make it harder
Offshore work and legacy replacement are each difficult on their own. Together they form the hardest case this handbook addresses, because the two problems compound: an offshore team needs more detailed requirements than a local one, at exactly the point in a project where requirements are least available.
A local developer may have used the current system, seen it work, absorbed some context by osmosis. An offshore developer has none of that — they have never seen the legacy system, don’t know how it behaves or why it was built as it was, and so everything must be explained explicitly. Nor can they resolve ambiguity by walking over to a user. Time zones make synchronous conversation hard, and a question that takes 30 seconds to answer locally can take 24 hours across time zones. The requirement therefore has to answer the question before the developer needs to ask it.
In practice that means every requirement carries its full context: the reason it exists, the business rules stated explicitly and shown with worked examples, the edge cases and error handling defined, clear acceptance criteria, mockups where the UI matters, integration specifications, the non-functional and regulatory requirements, and the test scenarios. This is the kind of complete, narrated requirement Jeff Sutherland calls an ‘enabling specification’ — a user story told with enough detail to remove the need for a follow-up conversation, without prescribing the implementation. It is a great deal of detail, and for an offshore team rebuilding a system they have never seen, it is necessary rather than excessive.
Refinement is what makes this work. Offshore teams need structured refinement sessions well before sprint planning, to review requirements together, answer questions, clarify ambiguities, find gaps and agree the approach, so that developers are never starting work on something they saw for the first time in planning. Two to three refinement sessions a week, working through the stories planned for upcoming sprints, is usually about right; by the time the work is picked up, the team understands it thoroughly. Everything the offshore chapter says about needing more structure rather than less applies here with the volume turned up.
Distance is the core problem: between the user who needs the software and the developer who builds it. Structure is what you add to close it. A legacy replacement run with an offshore team is simply that distance at its widest: across years of undocumented change, across time zones, across people who have never met. Get the requirements right and the distance stops mattering, because the requirement is the thing that has to travel as far as the work does.