Skip to content
Francesco Bilotta
Rescue & rearchitecting

The Strangler Fig Pattern for Zero-Downtime Legacy Modernization

How to replace a twenty-year-old monolith in production, one seam at a time, with no big-bang rewrite and no downtime. A four-phase method.

Francesco Bilotta · · 4 min read

Martin Fowler named the pattern after a plant. The strangler fig germinates in the canopy, sends roots down the trunk of a host tree, and grows around it limb by limb. Years later the host is gone and the fig stands in its exact shape, load-bearing, alive. That is how you replace a system that cannot be switched off. You grow the new one around the old one until the old one is no longer holding anything up, and then you remove it. Nobody downstream notices the moment it happens, because there is no moment. There is a sequence.

Phase 1Characterisation tests. Pin down what the system actually does today, quirks and all, before changing a line. This is the safety net.
Phase 2Find the seams. Cut along the real bounded contexts of the business, not the accidental boundaries the old schema imposed. Build new capability behind one seam.
Phase 3Run both roads. Old and new paths live side by side. Route a slice of traffic to the new path, verify it in production, then the next slice.
Phase 4Retire the limb. Once the new path fully covers the old one, delete the old code. Repeat until the monolith is gone and nobody noticed the switch.

Why the rewrite fails, and it is not your fault

The instinct with a twenty-year-old system is to start over. Read the old code, understand the domain, write it again cleanly, cut over on a weekend. This fails, and it fails structurally, not for want of skill. A system that has been in production for two decades has absorbed twenty years of corrections. Every ugly branch in the code is a business rule someone added after a real bug hurt a real customer. That knowledge is not written down anywhere. It lives in the branches, in three people's heads, and in nothing else. When you rewrite from scratch you are not reproducing a specification. You are trying to reconstruct undocumented behaviour from the outside, and you will miss the parts that only ever mattered on the one day a year they fired. The rewrite ships, the edge cases surface in production, and now you are debugging two systems at once with the old one already switched off. The big-bang rewrite asks you to be right about everything on a single date. Nobody is.

The strangler fig inverts the bet. Instead of one large irreversible cutover you make a hundred small reversible ones. Each is verified in production before the next. If a slice is wrong you route the traffic back, fix it, and try again. Nothing catastrophic is ever one deploy away.

Phase one: make the system tell you what it does

Before you change anything, you write characterisation tests. Not tests of what the system should do, tests of what it does. You feed it real inputs, capture the outputs, and freeze that behaviour as the specification, quirks included. On a system with no tests, no docs and no staging, this is the only honest baseline you have. It is also the part people skip, because it feels like effort spent producing nothing new. It is the opposite. It is the net that lets everything after it be aggressive. Software shows its worth when things go wrong, and this is the layer that tells you the instant something has.

Phase two: cut at the business seams, not the schema's

Now you find where to cut. The temptation is to follow the structure the old database already imposes. On the system I modernised, every query passed through a single God-table, so the schema's own boundaries were an accident of history, not a map of the domain. You ignore them. You cut along bounded contexts, the real seams of the business: the places where one part of the operation genuinely ends and another begins. Behind one such seam you build the new capability, in isolation, with its own model. Everything else keeps flowing through the old path untouched.

Phase three: run both roads at once

The new path goes live beside the old one. You route a thin slice of traffic to it, compare its output against the old path in production, and only when it matches do you widen the slice. This is where zero downtime is actually earned. The switch is a dial you turn slowly, not a lever you throw. When the histories of changes were themselves the product, I moved those contexts to an event-sourced model where the log is the source of truth. Everywhere else, where the state was all that mattered, plain JDBC with Flyway migrations was the boring, correct choice. You use event sourcing where history is the value, and nowhere else, because complexity you do not need is a cost you pay forever.

Phase four: remove the limb

When the new path fully covers the old one, you delete the old code. Not comment it out, delete it, so nobody routes back to it by accident. Then you take the next seam. On the S.p.A. system I did this alone, module by module, a twenty-year-old PHP and MySQL multi-tenant monolith becoming a Java and Spring architecture while the network kept working through every step. The people using it every day never had a bad Monday.

This is the work I do: modernise mission-critical systems while they stay in production, with zero downtime, until they are boring again. Boring is not an accident. It is a feature you engineer for, one seam at a time.