Code has become cheap. The knowledge embedded in your legacy system, however, is not.
On 23 February 2026, Anthropic published a post putting forward a bold thesis: The modernisation of legacy code had stalled for years because it cost more to understand it than to rewrite it. AI agents could take on precisely this analysis, enabling teams to modernise their COBOL systems “in quarters rather than years”. On the same day, IBM shares fell by 13 per cent, according to CIO.com, marking the biggest single-day fall since October 2000.
IBM responded with a blog post of its own. Rob Thomas, Senior Vice President responsible for software, noted: A literal translation captures almost none of the actual complexity. This lies in everything on which the application runs and to which it is connected. There is some truth in both points.
In day-to-day use, this complexity appears unremarkable. A rounding rule that the interface to the accounts department tacitly relies on. A status field whose value ‘9’ has meant something different for years than what’s in the manual. Rules like these aren’t found in any documentation. Anyone who overlooks them only realises it when the figures no longer add up after the migration.
That’s why every modernisation project begins with two questions. What options does the technology still allow? And how much knowledge do you need to preserve before you start rebuilding anything? When it comes to a mobile app, the article App update or build from scratch? app store deadlines and tech stack considerations.
What is a legacy system?
A legacy system is software that plays a key role within a company but can no longer be modified safely or cost-effectively. Age alone is not the deciding factor. A ten-year-old Java application with tests, an up-to-date runtime environment and two developers who are familiar with it is well-maintained existing software. A three-year-old project whose sole developer has left may already be considered legacy.
Legacy software therefore has three dimensions: the platform on which it runs, the people who understand it, and the knowledge embedded within it. Typical examples in small and medium-sized enterprises include PHP applications from the 2010s, Access databases with their own user interface, tools written in Visual Basic 6 or Delphi, applications based on older .NET Framework versions, AngularJS front-ends and ERP extensions that have grown over the years.
This should be distinguished from technical debt: the sum of the shortcuts in the code that make every further change more expensive. Technical debt is present in almost all software and can be reduced without replacing the system. ‘Legacy’ describes the state of the entire system, including operations and people. Application modernisation is therefore more than just tidying up the code.
How do you know that your software needs modernising?
The strongest argument against any modernisation is: ‘But the system is working, isn’t it?’ This is almost always true, but it answers the wrong question. The crucial point is whether you can still change it if you need to – for example, to address a security vulnerability, a new customer interface or the switch to e-invoices. These seven warning signs indicate that the answer is ‘no’ – or will soon be:
- The framework, runtime or operating system no longer receives security updates.
- Only one person can safely modify the system, either in-house or at a service provider.
- Minor changes take weeks because no one can predict what impact they might have elsewhere.
- There is no reproducible build. The latest working version exists only on a specific server or computer.
- Libraries cannot be updated because they require a newer platform.
- New requirements, such as an interface or mobile access, are thwarted by the architecture.
- You’ll struggle to find experts for this technology who are willing to get to grips with it.
The deadlines are set by the manufacturers. A few examples, as of October 2026: For AngularJS, officially ended in January 2022. PHP 8.0 and 8.1 will no longer receive security updates; 8.2 will receive them according to php.net until 31 December 2026, whilst 8.3 will receive them until 31 December 2027. Microsoft is ending extended support for Windows Server 2016 in January 2027; .NET Framework 4.6.2 will also reach end of support.
As a general rule, software continues to run after support ends, but newly discovered vulnerabilities are no longer patched.
Visual Basic 6 is the exception that illustrates just how limited the remaining support is. The development environment has has not been supported since 8 April 2008, whilst the runtime continues to function on current versions of Windows. However, Microsoft limits support to major regressions and critical security issues and expressly recommends replacing such applications with modern technology.
Added to this is the personnel risk. If only one person understands the system, their holiday poses an operational risk and their resignation constitutes an emergency.
What you must ensure before any modernisation
Before you decide on a course of action, ensure that the legacy system survives the modernisation: Code in a repository, tested backups, up-to-date base software, documented access procedures and tests that capture the system’s current behaviour. This may sound trivial, yet it is often overlooked because it does not yield any visible progress.
In concrete terms, this means:
- The source code is stored in full in a repository, and someone other than the original developer can build a working version from it.
- Backups exist, and a restore has actually been tested.
- The operating system, database and runtime are as up to date as the application allows. Where this is not possible, the system is isolated from the outside world as far as possible.
- Access rights, certificates and licences are documented and are not tied to private accounts.
- Characterisation tests are in place for the most important processes. They record what the system does today, even where it performs calculations incorrectly from a technical perspective.
The final point supports each of the following approaches. During refactoring, characterisation tests show that nothing has changed; with the Strangler approach, that the new module delivers the same results as the old one; and during a rebuild, which special cases the new software must cover. In short: only when you can measure the current behaviour can you change it safely.
This is what a characterisation test looks like
A characterisation test records what the legacy system actually does and checks every new implementation against it. To do this, you record the legacy system’s actual responses to typical and unusual inputs, for example from logs or via the existing interface. In the example, these pairs of input and response are stored in the file golden/fracht.json. The test checks whether the new freight cost calculation returns exactly the same result for every input.
// Characterisation test: records what the legacy system currently returns,
// and checks whether the new implementation returns the same result for the same inputs.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { calculateFreight } from './freight.ts';
// Previously recorded from the legacy system: real inputs, real outputs
const cases = JSON.parse(
readFileSync(new URL('./golden/fracht.json', import.meta.url), 'utf8'),
);
for (const case of cases) {
const { postcode, weightKg } = case.input;
test(`Freight for ${gewichtKg} kg to postcode ${plz}`, () => {
assert.deepEqual(berechneFracht(fall.eingabe), fall.altsystem);
});
}
This example uses only the test runner built into Node.js. Current versions of Node.js execute TypeScript files like this one without a build step. The key factor is the selection of test cases: boundary values, special customers and values with decimal places. If the new implementation rounds differently to the old one, this might not be noticeable at all for 12 kilograms, but it certainly will at 12.2 kilograms.
If a test fails, check with the business department to confirm whether the old behaviour was intended. Only then should you change the reference and record who approved the deviation.
Refactor, Strangler or Rebuild: Which approach is right?
Three approaches involve updating the code itself. Refactor is suitable if the technology is up to date or, at most, slightly outdated. ‘Strangler’ is suitable if the framework and version are significantly outdated, but the system is still running. ‘Rebuild’ is suitable if the framework has been end-of-life and, in addition, security updates for the version, maintenance or documentation are missing. In addition, there are three options that involve little or no modification of the code: retaining it, migrating it, and replacing it with off-the-shelf software.
| Strategy | What happens | Suitable when … | Greatest risk |
|---|---|---|---|
| Retain | The system remains in place, but is secured and monitored. | It is stable, rarely changed and runs on a platform that receives updates. | The decision will never be reviewed again. |
| Migrate (Rehost, Replatform) | The application is moved to new servers, into containers or to the cloud; the code remains largely the same. | Hardware or the operating system are the immediate problem; the software itself is still functional. | The maintenance issue within the code remains. |
| Refactor | Code, tests and platform version are modernised whilst remaining in use. | The technology is up to date or, at most, slightly outdated; the risk lies in maintenance, operation or documentation. | After the project, updates are once again neglected. |
| Strangler (gradual replacement) | A facade redirects one area at a time to a new system until the old one is taken offline. | The framework and version are significantly out of date, but the system is still running. | The transition phase is dragging on; two systems are running in parallel for longer than planned. |
| Rebuild (Rewrite) | A newly developed system replaces the old one on a specific date. | The framework has been end-of-life, and there are no security updates, maintenance or documentation available for the version. | Overlooked special cases only come to light after the migration. |
| Replace | Standard software takes over the task; the data is migrated. | The process is standard practice in the industry and is well reflected in widely used software. | Customisations to the off-the-shelf software become the next legacy system. |
The Legacy Check suggests a path based on the same rules, but for an entire system. In practice, you usually combine the approaches: the stable core is refactored, one module is replaced using the Strangler approach, and a third is replaced by standard software. You should therefore make the decision module by module.
Refactoring: modernising existing code
Refactoring means, in the strict sense, improving the internal structure of code without changing its behaviour. With legacy code, there is more to it: updating dependencies, upgrading the platform to a supported version, adding tests and a build pipeline, and documenting the knowledge that has so far only existed in people’s heads.
Refactoring is the most underestimated of the three approaches. A system that is one or two major versions behind the current state can quickly seem like a case for a complete rebuild, even if the business logic is still sound. For PHP, there is Rector, a tool that automates version upgrades and refactoring based on rules. This turns a dreaded upgrade project into a series of verifiable changes.
According to the manufacturer, Rector even covers the jump from PHP 5.3 to 8.5. A system which dates back that far is, according to the rule above, no longer a candidate for refactoring. Even if the upgrade can be largely automated, refactoring may still be the more cost-effective option. Only a look at the code will reveal whether this is viable.
The risk lies with the project. Refactoring makes a system maintainable again, provided someone actually takes on the maintenance. Without a fixed maintenance model involving regular updates, you’ll find yourself back in the same position in a few years’ time.
Strangler: replacing it whilst the system continues to run
The name goes back to Martin Fowler. He saw in 2001 in the Queensland rainforest, strangler figs that germinate within a host tree, grow around it and eventually replace it, and applied this image to the gradual replacement of software. The approach is now known as the Strangler Fig Pattern.
Technically, you place a façade in front of the legacy system, such as a reverse proxy or an API gateway. It decides on a per-request basis who responds: the legacy system or the new component. With each area that is phased out, the proportion of requests still reaching the legacy system decreases. Once no requests are reaching it, you switch it off. Users do not experience any ‘switchover day’ in the process.
Fowler justifies this approach with an observation familiar to anyone who has ever specified a new system: it is difficult to determine the existing behaviour in detail, and much of it is actually no longer needed. The step-by-step approach reduces risk and delivers benefits sooner. The cost is borne by transitional architecture – in other words, code that disappears again after the migration. Fowler considers this price to be justified.
A configuration for the nginx web server demonstrates just how little technical effort is required to get started. The ‘Offers’ section has already been replaced. The ‘Freight Costs’ section is running in shadow mode: the legacy system continues to respond, whilst the new service receives a copy of every read request.
# Facade in front of the legacy system: Replaced sections are routed to the new
# service; everything else continues to the legacy system unchanged.
upstream legacy-system { server legacy-app:8080; }
upstream new_service { server new-service:3000; }
server {
listen 80;
# Section 1 has been replaced: quotes come from the new service
location /api/quotes/ {
proxy_pass http://neuer_dienst;
}
# Section 2 in shadow mode: The legacy system continues to respond,
# the new service receives a copy, but its response is discarded
location /api/freight-costs/ {
mirror /shadow;
proxy_pass http://altsystem;
}
location = /shadow {
internal;
if ($request_method != GET) { return 204; } # mirror read-only requests only
# Short timeouts: A new service that hangs must not slow down the old system
proxy_connect_timeout 1s;
proxy_read_timeout 1s;
proxy_pass http://neuer_dienst$request_uri;
}
# Everything that hasn’t been migrated yet
location / {
proxy_pass http://altsystem;
}
}
The copy is generated by the nginx mirror module, which discards the responses to mirrored requests. Users therefore continue to see only the old system. You set up the synchronisation yourself, for example by having the new service log its results and a job compare them with the responses from the old system.
Only read requests are mirrored. A mirrored write request would create data a second time, this time in the new system. Equally important are the two timeouts in the shadow block: nginx only releases the connection for the next request once the copy has also responded. Without them, a hung new service would slow down real users, in extreme cases up to the default value of 60 seconds. With them, the delay remains at around one second.
The first section is best suited to content that is clearly definable and changes frequently, as this is where new technology pays off the quickest. The version history reveals how often a section changes, whilst the façade’s access log shows how heavily it is used. One common misconception remains: the Strangler approach does not require microservices. A modular monolith with modules organised along business boundaries is often the better goal. The article on Domain-Driven Design shows you how to identify these boundaries.
Rebuild: rebuild when nothing is working anymore
A rebuild is the right course of action if the framework has been end-of-life’d and there are no security updates, maintenance or documentation available for that version. In such cases, there is no secure way to continue operating the system, and the legacy check also recommends rebuilding it.
A rebuild does not start from a blank page either. The requirements are embedded in the running system: in forms, reports, database fields and in the knowledge of the people who work with it every day. This results in a requirements specification, which describes what the new software must be capable of. The guide to Requirements Specification and Specification Document.
For the data migration, the advice given further down in the section on data applies: test early, using the complete dataset. Right up until the system is taken offline, the recorded reference cases from the characterisation tests will be run against the new system, and any discrepancies will be clarified by the relevant department. You should also use the new system to streamline operations: anything that, according to usage data, nobody accesses anymore will not be replicated.
If the new system is adopted, custom software will be developed to suit your current workflows. For an initial estimate of the costs and duration of such a project, use the project calculator.
Keep, migrate or replace: the other options
Not every legacy system needs new code. A stable programme that is rarely modified and has a clearly defined purpose may be retained if you isolate it and review the decision regularly. If the problem lies primarily with the hardware or operating system, migrating to new servers or containers will buy you time. Anthropic describes this approach as quick, but notes that the maintenance issue remains.
The decision guide on Cloud Migration. If, on the other hand, the legacy system supports a standard process, off-the-shelf software is often the more cost-effective solution. The article Build vs. Buy from an engineering perspective.
How do AI coding agents change modernisation?
AI coding agents can make the analysis of a legacy system significantly more cost-effective: mapping dependencies, documenting forgotten processes, identifying risks and designing tests. The decisions remain with the team. No agent can answer which business rules still apply, whether the tests are sufficient, or when the switchover will take place.
This is also how Anthropic describes it: Claude Code is intended to take over the exploration and analysis phases, which account for the bulk of the effort in COBOLprojects, and to design functional tests in advance that verify whether the migrated code produces the same output as the original. This is the principle behind the characterisation test mentioned above. Nevertheless, Anthropic believes the greatest benefit lies in rewriting the code in a modern language.
For legacy systems in small and medium-sized enterprises, I consider a different benefit to be more important. Refactor and Strangler thrive on understanding what already exists and capturing it in tests, and it is precisely this preparatory work that is becoming cheaper. An agent that creates a dependency map and suggests reference cases for characterisation tests lowers the barrier to entry for the incremental approach more significantly than for a complete rewrite.
What AI doesn’t change: only the business department knows which of the identified rules still apply. An agent that translates code into a new language without tests simply carries the errors over, only faster. And the migration, involving interfaces, data transfer and a go-live date, remains an organisational project. This is the crux of Rob Thomas’s objection.
In practice, you deploy AI agents for four tasks: assessment, test designs, mechanical upgrades and initial documentation, which subject matter experts proofread. Every change made by an agent goes through review and testing just like that of a human team member.
Why data and interfaces determine the timeline
Data and interfaces determine the timeline because problems with them only become apparent at a late stage: A forgotten connection only becomes apparent when a partner stops receiving data, whilst legacy issues in the inventory only surface during the first full migration. You should therefore plan three things early on: an inventory of all connections, exactly one write system per table, and a repeatable data migration.
Over the years, the shop, ERP, accounts, reports and file exports. Each of these interfaces must continue to function after modernisation or be deliberately phased out.
The front-end provides part of the inventory straight away. After a few weeks, its access log shows which endpoints are actually being accessed, by which systems and how often. Anything that never appears there is a candidate for removal. However, you won’t find connections bypassing the front-end this way – such as direct database access or file storage. You’ll need to look for these in the database user accounts, in scheduled jobs and in network shares.
As long as the old and new systems are running in parallel, each table requires exactly one system to write to it. The other system reads from it or receives the changes via synchronisation. If both write to the same data, they overwrite each other, and this is rarely noticed straight away. With each migrated area, write control for its data changes exactly once, as planned and on a specific date.
You should write the data migration as a repeatable script and run it regularly from the outset against a copy of the complete, pseudonymised if necessary, from the very start. After each run, you compare row counts and totals between the old and new systems, such as open items or stock values. Any discrepancy becomes a ticket that must be resolved before the actual migration.
When dealing with the target system, take care not to lock yourself into the next dependency. Open data formats, documented interfaces and code for which you hold the rights prevent a new vendor lock-in.
What does it cost to modernise legacy software?
The cost of modernising legacy software depends on six factors: business logic, interfaces, data quality, test coverage, documentation and the required availability. Added to this is the chosen approach, as a refactoring involving updates and tests is a different project to a rebuild involving data migration and parallel operation. A reliable figure can therefore only be provided after the initial assessment; you can estimate the factors beforehand.
| Cost drivers | Why it matters | How to influence it |
|---|---|---|
| Business logic and edge cases | Every rule must be understood, tested and either re-implemented or deliberately removed. | Measure usage and remove unused functions before the project begins. |
| Interfaces | Every connection requires testing and often adjustments on the other side. | Record actual calls via the façade log and involve partners at an early stage. |
| Data quality and data volume | Data cleansing and repeated test runs take time, which is rarely factored into the schedule. | Build the migration script early and run it regularly against the entire dataset. |
| Test coverage at the outset | Without tests, every behaviour must first be verified. | Write characterisation tests for the most important processes first, preferably with AI support. |
| Documentation and subject matter experts | If both are missing, the analysis becomes the biggest item. | Involve subject matter experts early on and record their explanations in writing. |
| Availability during operation | No downtime means parallel operation and transitional architecture. | Clarify possible migration windows with the business department before the architecture is finalised. |
On the other side of the equation are the costs of inaction: maintenance that takes up more and more time each year, unresolved security risks and dependence on a small number of individuals. These do not feature in any quotation. Only by looking at the Total Cost of Ownership over several years makes both sides comparable.
Roadmap: Five steps to a decision
The path from gut feeling to a sound decision involves five steps. The first two cost little and reduce risk immediately; the remaining steps prepare the ground for the actual modernisation.
- Assess the risk. The Legacy Check asks six questions about the language and framework, version, last update, maintenance, hosting and documentation. In two minutes, it provides a risk score from 0 to 100 and an initial course of action, without the need to register.
- Secure operations: repository, tested backups, documented access rights and characterisation tests for critical processes.
- Take stock: modules, interfaces, data flows and actual usage. This is where AI agents are most helpful.
- Define a path for each module: retain, refactor, phase out gradually, rebuild or replace. The order depends on where the first step delivers the quickest visible benefit.
- Start small and agree on a maintenance plan. The first module goes into production before the second is planned, and a maintenance model ensures that the new system does not become obsolete again.
The page Replacing legacy systems describes how we take these steps together with you.
Frequently asked questions on legacy modernisation
What is the difference between refactoring and a rebuild?
With refactoring, the system remains in place. You improve the code, tests and platform version without changing the business logic. With a rebuild, a newly developed system replaces the old one, usually involving data migration and parallel operation. Refactoring is suitable if the technology is up to date or, at most, slightly outdated. A rebuild becomes necessary if the framework has been end-of-life and there are no security updates, maintenance or documentation available for that version.
Can I modernise legacy software without interrupting operations?
Yes, that is the aim of refactoring and the Strangler approach. With refactoring, changes go live in small releases. With the Strangler approach, the legacy system remains in operation whilst a façade redirects individual areas to the new system. Only a rebuild has a migration deadline, which you can keep short by running systems in parallel and carrying out a tried-and-tested data migration.
Can AI automatically translate legacy code into a modern language?
To some extent. AI coding agents analyse dependencies, document workflows, design tests and carry out routine upgrades. It is up to humans to decide whether a rule identified still applies and when the migration should take place. A translation without testing also carries over the errors from the legacy system into the new technology.
How long does legacy modernisation take?
That depends on the approach and the scope, particularly on the number of interfaces and the quality of the data. More important than the total duration is when the first part goes live. Refactoring and the ‘strangler’ approach deliver results in stages, whilst a rebuild is only completed by the deadline. A reliable estimate can be made once the current state has been assessed.
What happens if the framework or version no longer receives updates?
The software continues to run, but no one will patch any newly discovered security vulnerabilities. In the short term, isolation helps: allowing access only from the internal network or via a front-end proxy. In the medium term, the rule mentioned above applies. If only your version no longer receives updates, but the framework does, the ‘strangler’ approach is suitable, or a refactoring if the upgrade can be largely automated. If the framework has also been end-of-life, it’s a case for a rebuild.
Conclusion: First secure the knowledge, then choose the path
Modernising legacy software could become cheaper by 2026, as AI agents shorten the costly analysis phase. This does not alter the criteria for the decision. It still depends on whether the framework and version are still receiving updates, whether someone maintains and understands the system, and how much domain knowledge is embedded in the code.
My position: If a legacy system is up and running, you should either modernise it whilst keeping it in operation or phase it out in stages, provided the regulations allow it. First, secure the system; then assess the existing setup; decide on a course of action module by module; and start small. A complete rebuild remains the exception for end-of-life technology where there is no reliable way to continue operating it. My advice to get you started: Write the initial characterisation tests before anyone discusses the technical aspects. You’ll need them for any of the three approaches.
The free Legacy Check shows you just how modern your software really is in two minutes. If you’d like to discuss the results with someone who specialises in such migrations, you can find out about our approach to software modernisation.