Skip to content

When a legacy system needs replacing — and when it doesn't

Pedro Cunha
Pedro Cunha
CTO at Epicora

Published on
updated on · 22 min read

In short

Old, ugly or slow is not a reason to replace: of the nine systems that were already running when we arrived, we replaced two and still maintain five. The signs that actually decide are different ones — nobody can change the system, you lost access to your own product, the team has already built a path around it. And before any of them, an audit of what you already have: in a recent case, three of the client's four requests already existed in the current system, switched off.

"It's old and it's ugly" is not a reason to replace#

An old, ugly or slow system is not a reason to replace it. Of the nine legacy systems Epicora has taken over, two were replaced and five are still under maintenance. Age is not a defect: a dated interface costs a fraction of a rewrite to fix, and a system that has worked for years is an argument in its favour, not against it.

When someone comes to us to replace a system, the opening sentences are almost always the same: "I built this a long time ago", "I'd like it to look better, it's really ugly", "it's old".

None of the three is a reason. Age is not a defect, a dated interface is fixed for a fraction of what a rewrite costs, and "a long time ago" describes a system that has been working for a long time — which is an argument in its favour, not against it.

The best-known warning about this is from 2000: in "Things You Should Never Do", Joel Spolsky calls a rewrite from scratch the worst strategic mistake a software company can make. The argument is about reading, not about code — it is harder to read code than to write it, so the old system looks worse than it is. What comes across as a mess is usually years of accumulated fixes: every odd line solving a real case nobody remembers any more.

Our own practice confirms it. Over the past few years we took over nine systems that already existed when we arrived. We replaced two.

SystemWhat it wasWhat we did
Reticarfield sales rep app, unmaintained, pulled from the storereplaced
DNA Genéticabreeding system over ten years oldreplaced
Management system with around 18 modulesin daily use, the whole operation inside itreplacing now
GTapp + web platform + API inherited from another teamrebuilt the app only
Cirurgicredold system, no documentationmaintaining
Hybrilive event platformmaintaining
UAI Legalthird-party systemmaintaining
Two legal platforms from the same vendoruntyped JavaScript, no documentationmaintaining

The most telling case is Cirurgicred. The system is old, has no documentation and did not work correctly — and the client came to us wanting to evolve it. Our conclusion was that evolving it made no sense: small adjustments went in to follow the current shape of the operation, and no new features. Saying that costs revenue in the short term and is the right recommendation.

The costliest mistake: the system is not insufficient, it is unconfigured#

Before writing scope for a new system, audit the current one from the inside, logged in. In one case in August 2026, three of the four requirements the client asked for already existed in the system in use — switched off. A requirement that shows up disabled is configuration and costs a fraction of the price; a requirement with nowhere to live in the data model is software.

There is a request that arrives with the decision already made: "the system we use serves us well, I just wanted one of our own, with these four improvements". It looks like the easiest one to fulfil, and it is the one that misleads the most.

In August 2026 we did, in that case, what we now do every time: before writing a line of scope, we went into the current system logged in, from the inside, with access granted by the client himself. It was a vertical management SaaS, in use for five months. Of the four requirements he asked for, three already existed there — switched off:

  • Self-scheduling by the end customer was active, with a public page live — and 1 of 120 services published on it.
  • The pre-booking he asked for existed under another name, configurable service by service, and sat at zero across all 120.
  • The third was a messaging module the system offered and that had never been activated.

The fourth requirement was real — and deeper than the request. It was not a filter missing from a screen: the data that filter would need did not exist in the records, and the two sides of the operation measured capacity in different units. That is the requirement that justifies software.

None of this shows up in a briefing, because the client describes what he believes the system does not do — and nobody knows their own system in full. The discount the audit gives the project is large: there was an honest path that delivered three of the four requests through configuration, without building anything.

The second effect is less obvious and worth more: an under-configured operation is a project risk. If the current system reached the five-month mark with 119 of its 120 services off the public page, the new system — more capable, better looking, with the four improvements — arrives switched off just the same. What was missing there was not software.

The script fits in one session, and the order matters: the screen where the pain is, and the filters it actually has; the records behind it, to see whether the data exists; the whole menu, which is the real yardstick for "it serves us well"; the customer base sorted by spend, which tends to contradict the persona in the briefing; the physical capacity of the operation, which is the bottleneck ahead of any screen; and how many catalogue items actually bill. It is the same principle of diagnosing before deciding that we apply to AI-generated code: the question "what can be salvaged?" is always cheaper than the answer "let's rebuild".

One note on conduct, because it matters to anyone repeating the exercise: a SaaS agreement usually forbids sharing access and replicating the software. Access has to be granted by the client — ideally with a dedicated user for whoever is auditing —, what you document is his operation and his requirements, never the vendor's implementation, and you do not accept terms or write data inside a third party's system.

Sign 1 — nobody can change the system#

The first sign that a system needs replacing is not technical: it is that nobody can change it. The test takes a minute — how many people can, today, fix a bug in production and ship the fix? If the answer is one, you have a continuity risk. If it is zero, the system has stopped being an asset and became a liability.

This is the sign that shows up disguised as small talk: "the company that built it doesn't exist any more", "the developer doesn't want to work on the project", "nobody really knows how it works".

Notice that none of them is about technology. They are about people and knowledge, and that is what makes them serious: a technically healthy system nobody knows how to change is closer to the end than an ugly system with an active team.

We have seen all three variations:

  • At DNA Genética, the system was over ten years old, had been built by a software house and had no clear documentation. What was left was one programmer doing occasional maintenance who did not want to keep developing it.
  • At Hybri, the person who built it was a developer who was a partner in the project. He left, and the platform was left with nobody.
  • At GT, the app had been written without typing and without documentation — the knowledge was neither in the code nor on paper.

The test is objective and takes a minute: how many people can, today, fix a production bug and ship the fix? If the answer is one, you have a continuity risk. If it is zero, the system has already stopped being an asset and become a liability — it just has not sent the bill yet.

Sign 2 — you lost access to your own product#

The second sign is losing access to your own product: the app was pulled from the store, the publishing account belongs to a vendor who disappeared, or a dependency has a shutdown date. The question that sums it up is blunt — is there a date, set by another company, on which your system stops working?

This is the most concrete sign of all, and the least written about. It is not about code quality: it is the moment when a third party decides the fate of your system and you have no way to respond.

It shows up in very literal forms:

  • The app left the store. That is what happened to Reticar's old app: it stopped keeping up with the required SDK versions and was removed. The client could no longer install their own app — that is not a metaphor for obsolescence, it is an app that no longer exists for the people who need it.
  • The app never reached the store. At GT, the inherited app was distributed as a file, straight to the client, and the team still had to walk each person through installing it.
  • A dependency is going to be switched off. Hybri's platform ran scripts on a version of Node that was going to be discontinued. The system worked fine — what was about to break was the ground underneath it, on a date that was not the client's.

In both app cases there is a question that comes before the technical one and decides the rest: whose store account is it. If it belongs to the vendor who disappeared, you did not just lose the app — you lost the channel, and starting over in a new account costs the installed base, the review history and the update path for everyone who already had it. We covered that in who should own the App Store account.

The same applies to the platform the system was written on. Vue 2 reached end of life on 31 December 2023: according to the project's official page it no longer receives features, updates or fixes — but it remains available on the distribution channels. That is where the risk lives: end of life does not knock anything over, so nothing warns you. TinyMCE 4, a common editor in management systems, has been unsupported since 31 December 2020 — five years without a security fix, still running in production out there.

The question that sums up the section: is there a date, set by another company, on which your system stops working? If there is, the deadline is not yours.

Sign 3 — the team has already built a path around it#

The third sign never opens a ticket: the team stops using the part that does not serve them and works around it, in spreadsheets, WhatsApp and documents assembled by hand. When that happens the system has already been partly replaced — with no project, no control and nobody accountable for the data that now lives outside it.

This is the sign the board finds out about last, because it never raises a ticket: the team simply stops using the part that does not help and solves it outside the system.

At DNA Genética, collecting animal traits in the field was done with pen and paper and typed in later — the system existed, but it did not follow the person standing in the pasture. At another client, the internal messaging module sat idle because the team talked on WhatsApp, and a report the system was supposed to produce was assembled by hand in Word, every month.

Nobody decides this in a meeting. It just happens: someone builds a spreadsheet to double-check a number the system shows wrong, someone else starts sending the document over WhatsApp because sending it from inside fails, and within a year the real operation is scattered across five tools nobody purchased.

When that happens, the system has already been partly replaced — by spreadsheet, WhatsApp and Word. The question stops being whether you should replace it and becomes whether you noticed the replacement already started: with no project, no control and nobody accountable for the data that now lives outside.

Sign 4 — the system became the limit of the business#

The fourth sign is performance changing what the company is able to do — not one slow screen, which is usually a bad query and does not justify replacing anything. At DNA Genética, 65 animals in three batches took 1 hour and 35 minutes on the old system; on the new one, with twice as many animals, the same operation answers in up to 3 minutes.

Slowness on its own is not a sign: a heavy screen is usually a badly written query, and swapping the system because of it is far too expensive. The sign is when performance changes what the company is able to do.

At DNA Genética this was measured with a stopwatch, on the old system, at a real farm. A batch of 32 animals took 30 minutes just to run the breeding match, plus another 19 minutes to print the result — with an error midway that forced a restart. The whole farm, 65 animals across three batches, consumed 1 hour and 35 minutes. On the system we built, tested with twice as many animals, the same operation — run and print — responds in up to 3 minutes.

The number matters less than the consequence: at 1h35 per farm, it was impossible to run the breeding match in front of the farmer. The work had to be taken back to the office and returned later. It was not a slow screen; it was the system deciding how the service could be delivered.

The other form of this sign is quieter: changing one thing starts costing two. In a management system we diagnosed, two practically identical modules had been implemented in parallel, taking up ten tables where four would do — and every new rule had to be written twice, or the two sides drifted apart. In that same system, partner data was stored as fixed configuration keys: adding a new partner was not configuration, it was editing data. In two legal platforms we maintain, written in untyped and undocumented JavaScript, the effect arrived by another route: each requested improvement took so long that the client's product stopped moving.

Summary of the four signs, with what usually sits behind each sentence:

What the client saysWhat usually sits behind itPatch or replace
"It's ugly, I want to modernise it"dated interfacepatch — a redesign costs a fraction
"It's slow"a badly written query on one screenpatch — unless it changes the process
"The company that built it is gone"nobody accountable for the codeassess — start by taking over maintenance
"The app vanished from the store"nobody kept up with the platform's requirementsreplace the affected part, urgently
"We control that in a spreadsheet"the system has already been replaced from outsidereplace — or lose the data for good
"This part can't be changed"a business rule became data, or duplicated codereplace when the cost of changing beats the cost of rebuilding

Can you replace just one part?#

You can — and that is the answer that saves the most money, when it fits. The criterion is whether the part is genuinely separable from the rest.

GT is the example. We inherited from another team a set of systems that came out of a bad experience: the project took four times the estimated schedule and did not deliver the full result. The app had no typing, no documentation and had never been published. Even so, we did not rebuild everything. The app is detachable — it talks to the platform through a stable interface — so we rebuilt only that: typed, documented, with an approved prototype and published to the stores. The web platform and the API stayed, running on Go.

In the two legal platforms we maintain, the gain came from an even smaller cut: we swapped the e-signature tool for another one, lowering cost, without rewriting a single line of the system.

There is an established name for gradual replacement: the Strangler Fig pattern, described by Martin Fowler — build the new around the old and move function after function across until the old one can be switched off. It works, and the condition is always the same: clear boundaries between the parts. Without that boundary, going gradual gets expensive — data that drifts apart between the two systems, two integrations instead of one, and a transition architecture that usually outlives the plan. At that point the fastest route is the direct one: touch the current system as little as possible and build the new one whole.

There is also a third route, when part of what the legacy system does is well done and the operation does not want to lose it: the old one stays as the source of data and the new one is built around it. The warning from any integration applies here — what the architecture can do is not your choice, it is whatever the other side offers, as we described when integrating with the ERP the operation already uses.

The legacy system is the specification — and that is where the new project goes wrong#

Once the decision to replace is made, the next mistake is a subtle one: treating the old system as the problem instead of as the most complete source of requirements that exists. It carries years of rules nobody ever wrote down, and the test that you understood it is not "the new one is better" — it is the new one does everything the old one did.

In the management system we are replacing right now, that test failed our own design. Before writing code we built a clickable prototype of the entire system — 44 routes today — under one hard rule: nothing enters development before written sign-off. Re-checking the prototype against the system in use, screen by screen, surfaced what the scope had missed: the central working screen had two sections where the current system already has five. It was not a new request from the client; it was a capability the legacy system has delivered for years and that we had left out.

The same exercise produced the opposite lesson, which matters just as much: replacing is not copying screen by screen. A whole module of the old system became a flag inside another screen — because the question it answered was a single one, and having its own module forced people to upload the same file in two places and decide, before attaching it, which of the two it belonged to.

Two practical conclusions come out of that, and they separate a calm cutover from one that halts the operation:

  • Reconciliation is against the system in use, not against the document. Comparing the new scope with the old model, or with the memory of whoever used to operate it, lets behaviour slip through. What works is opening both side by side.
  • The prototype is the cheapest instrument for discovering undocumented rules. Far cheaper than discovering them later, with the new system in production and the team unable to close the month.

If you are replacing the whole thing, how the cutover happens#

Replacing a system that is in operation is not living with two systems: it is building the whole thing and cutting over at once.

The path has four stages. The new system is built in full, with partial deliveries going to staging throughout development — the client validates in pieces, but does not operate in pieces. Once ready, it runs in parallel with the old one for a period: the team uses both, compares, and points out what is missing. The cutover happens on an agreed day, with data migrated in a planned window. And the old system stays read-only for a few months as a fallback, before being switched off.

The parallel run is the misunderstood part. It is not for splitting the operation between the two systems — it is for the team to gain confidence before depending on the new one. At Reticar it went like this: during testing the new app ran alongside the old one, with in-person training and a formal round of feedback; only then were the credentials handed over and the old one retired. The migration carried 3,819 customers, preserving identifiers and registration dates — the sales rep opened the app and recognised their own portfolio, instead of an empty system asking them to retype years of relationships.

One scheduling detail is worth more than it looks: the cutover date is chosen against the operation's peak, not against the project timeline. If the operation concentrates between January and May, the right window is between August and October — even if that means waiting.

Frequently asked questions#

How do I know whether the problem is the system or the way it is configured?#

By going into the current system before writing any scope — logged in, with the people who operate it beside you. The check is objective: for every missing capability, look for the field it would require in the records and the matching item in the menu. A requirement that shows up switched off is configuration, and it costs a fraction of a new system. A requirement with nowhere to exist in the data model is software. In a recent case, three of the client's four requests were already in the system — switched off.

Can we replace the system gradually, one module at a time?#

Yes, when the part is genuinely separable — an app that talks to the platform through a stable interface can be rebuilt on its own. When it is not isolated, splitting the operation across two systems gets expensive: data that drifts apart, two integrations instead of one, and a temporary architecture that outlives the plan. At that point it is faster to build the whole thing and cut over at once.

How long does it take to replace a system the company uses every day?#

For a full management system, with many modules and years of accumulated data, the realistic span between the start of development and the cutover is ten to twelve months — and much of that time is not writing code, it is uncovering the rules nobody documented. For a standalone app or module, it is a fraction of that. In both cases the old system keeps running until cutover day.

Do I need the old system's source code to replace it?#

It is not mandatory, but it changes the cost and the risk. Without it, business rules have to be rediscovered by reverse engineering — watching the system in use and interviewing the people who operate it — which is slow and leaves room for divergence. With the code, the implicit becomes verifiable. If the previous vendor still exists, negotiate access before you start.

What happens to the data and the history from previous years?#

It migrates, and that is the most underestimated part of the project. The thing to watch is preserving original identifiers and dates, so that whoever opens the new system recognises their own records instead of an empty screen. The migration has to be rehearsed more than once before the cutover, and the old system usually stays available read-only for a few months afterwards.

My vendor disappeared or stopped answering. Is that reason enough to replace?#

It is reason enough to act, and replacing is the most expensive option available. An absent vendor is a contract problem, not a code problem: in most cases the way out is handing maintenance to another team, which takes the system as it stands. That also works as a cheap diagnosis — after a few months living with the code, replacing or not stops being guesswork.

Sources#

Next step#

If you recognised two or more of these signs, the next step is not to request a rewrite proposal — it is to run a diagnosis. In most of the cases we have handled, the right outcome was taking over maintenance and fixing what was blocking the operation. When replacing is the answer, it usually applies to the whole: that was the case at Reticar Motores, where we replaced the field sales system of a live operation. It is the kind of decision we work through in custom systems — and, if your team works where the signal drops, it is worth also reading what changes in a system when the app has to work with no signal.

Share
Pedro Cunha
Who writes here
Pedro Cunha
CTO at Epicora

Pedro Cunha leads engineering at Epicora, in Chapecó, Brazil. He writes about the technical decisions behind the systems the team puts into production — architecture, scope, estimation and applied AI.

Articles by Pedro Cunha

Contact

Let's talk about your project

Tell us what you need to solve. We reply fast, with people who understand both technology and business.

Prefer to talk directly?

Pick the channel you prefer. We reply fast, during business hours.

From the first conversation to go-live: efficiency, security and innovation.