Legacy System Modernization: The Strangler Fig Pattern in Practice
Why full rewrites fail
The temptation when facing a painful legacy system is familiar: “Let’s throw it all away and rewrite from scratch.” The history of software engineering is littered with rewrite casualties. Netscape tried it and spent three years shipping a browser while Firefox ate their market. Rewrite estimates always underestimate the accumulated complexity of the original system, because that complexity is not accidental. It represents years of business rules, edge cases, and decisions that nobody remembers but that keep the system running.
The strangler fig pattern offers an alternative: replace the legacy system piece by piece, keeping the old system running until there is nothing left to replace. It is the strategy we recommend in 90% of cases. It is not glamorous. It is not fast. But it works.
The strangler fig pattern, explained
The name comes from the strangler fig tree, a tropical species that grows around its host, feeding on its nutrients, until the original tree dies and the fig stands in its place. The software parallel is direct: the new system grows around the old one, progressively assuming functionality, until the legacy can be shut down.
In technical terms, the pattern has three phases:
Phase 1: Intercept. A proxy or facade is placed in front of the legacy system to intercept all requests. Initially, the proxy simply redirects everything to the old system. Nothing changes. But it establishes the control point necessary for incremental replacement.
Phase 2: Replace. Functionality by functionality, new implementations are built. The proxy redirects traffic for each replaced function to the new system. The legacy system continues handling everything not yet migrated.
Phase 3: Eliminate. When all relevant functionality is in the new system, the legacy is shut down. In practice, this phase usually leaves a residue: features nobody uses but nobody dares to remove. The strangler fig forces you to make that decision explicitly.
The anti-corruption layer
The biggest risk in a strangler fig migration is not technical. It is that the new system inherits the old system’s technical debt. If the new orders service talks directly to the legacy database, the legacy data model contaminates the new system’s design.
The solution is an anti-corruption layer (ACL): a translation layer between the new system and the legacy. The ACL translates legacy domain concepts to new domain concepts and vice versa. An “ORDER_HEADER” with 47 nullable fields in the legacy database becomes a clean Order object in the new system, with only the fields the business actually uses.
The ACL has a cost: it is additional code to maintain during the migration. But that cost is trivial compared to the alternative of building a new system that replicates the old system’s defects.
In practice, the ACL is implemented as:
- A database adapter that reads from the legacy schema and exposes a clean model
- A translation service that converts legacy events to new domain events
- An API gateway that transforms requests and responses between formats
Which one? It depends on how the systems communicate. If they share a database (common in legacy monoliths), the database adapter is the starting point. If communication happens via API or messaging, the translation service is more appropriate.
Data synchronization during migration
The hardest problem in a strangler fig migration is not moving functionality. It is keeping data synchronized between the old and new systems during the coexistence period, which can last months or years.
Three synchronization patterns exist:
Dual write: the new system writes to both databases. The most intuitive pattern and the most dangerous. If one write fails and the other succeeds, data desynchronizes. Without distributed transactions (which add their own complexity), dual write is a time bomb.
Event sourcing: every change produces an event that both systems consume. The most robust pattern but also the one requiring the most infrastructure (you need an event broker like Kafka or RabbitMQ) and the most changes to the legacy system (which must start publishing events).
Change Data Capture (CDC): a tool monitors changes in the legacy database (typically via the transaction log) and replicates them to the new system. Debezium is the open-source standard for this. The advantage: no changes required in the legacy. The disadvantage: you are coupled to the database schema, and schema changes can break synchronization.
Our recommendation: CDC with Debezium for the initial phase (because it requires no legacy changes), evolving toward event sourcing as the new system assumes more functionality. Dual write only as a last resort, and with reconciliation mechanisms.
Deciding where to start
Not all functionality migrates equally easily. Priority should be based on three criteria:
Business value: which functionality generates the most pain today? If the billing module takes 20 minutes to generate an invoice and the admin team loses 3 hours daily, that is a priority candidate.
Independence: which functionality has the fewest dependencies with the rest of the system? A reporting module that reads data but does not modify it is easier to extract than the core order processing.
Risk: which functionality is safest to migrate? Starting with low-risk features lets you test the migration infrastructure (proxy, ACL, synchronization) before touching critical components.
The intersection of high value, high independence, and low risk is the ideal starting point. A feature that is perfect on all three axes rarely exists, but the combination of at least two usually identifies a good candidate.
What the textbooks do not mention
The migration takes longer than estimated. Always. Plan for 50% margin. Business rules hidden in the legacy system surface when you start replacing functionality, not when you analyze it from the outside.
The team must maintain two systems. During migration, the team supports the legacy and builds the new one. If capacity is not sized for both, either the legacy degrades (because nobody wants to work on it) or the new system stalls (because the legacy absorbs all attention).
The definition of “done” is fuzzy. When do you shut off the legacy? When all functionality is migrated, say the textbooks. In reality, there is always 5-10% of functionality that nobody uses enough to justify migration but nobody dares to eliminate. That decision must be made explicitly. Our rule: if a feature has not been used in 6 months, it does not get migrated. It gets documented and shut down.
The proxy is the most critical piece. If the proxy that intercepts traffic fails, everything fails. It must be extremely reliable, with failover, metrics, and alerts. It is not a glamorous component, but it is the one keeping the migration standing.
A pattern, not a recipe
The strangler fig is not a step-by-step recipe. It is a pattern that requires adaptation to each situation. A 15-year-old Java monolith with an Oracle database requires a different implementation than a PHP application with MySQL. The principles (intercept, replace, eliminate) are universal. The details are always specific.
What is universal: the patience required to do it well. A successful legacy migration is measured in quarters, not sprints. But every migrated feature is an irreversible step toward a better system. And unlike a full rewrite, each step delivers business value immediately.
If your organization faces a legacy system that is slowing innovation, we can help design an incremental modernization strategy. Our custom development team has experience with strangler fig migrations across sectors including logistics and legal. See also our analysis of the real cost of technical debt to quantify the impact of maintaining the status quo.
About the author
abemon engineering
Engineering team
Multidisciplinary engineering, data and AI team headquartered in the Canary Islands. We build, deploy and operate custom software solutions for companies at any scale.