What you'll learn: By the end of this lesson you will be able to explain the strangler fig pattern and its three phases, find a seam and choose the first slice to extract, route and ramp traffic through a facade, move a slice's data without two writers, bridge old and new with an anti-corruption layer, recognise when the pattern does not fit, and plan how a migration actually finishes.
A restaurant cannot close for a year to rebuild its kitchen. So the owner rebuilds one station at a time: the cold station moves into a new room, orders for cold dishes are passed there, and the old station is dismantled only after a few busy weeks without trouble. Diners see the same menu throughout. The risk of each step is small, and a bad step can be undone by passing the orders back.
That is the pattern in small. Architecture styles introduces it as a way to split a monolith, microservices decomposition says where the boundaries go, and ShopStream Stage 8 lists its steps. This lesson is the dedicated treatment: seams, the facade, the data, the bridge between old and new, and how it ends.
Verified against Martin Fowler's article, Microsoft's Strangler Fig and Anti-corruption Layer pattern pages and AWS Prescriptive Guidance, read on 2026-11-08. The Java sample ran on Java 26.0.1 that day.
The core idea
The strangler fig pattern replaces a legacy system gradually: new components grow around it, traffic moves to them piece by piece, and the old parts are retired once nothing depends on them. Martin Fowler took the name from strangler figs he saw in the rainforests of Queensland in 2001: a vine germinates in a host tree, grows to the ground and can outlive the tree. He argues against the simple plan of replacing everything at once, which, he says, "we've seen ... go down in flames most of the time": users cannot wait for new features, existing behaviour is hard to specify, and much of it is unwanted anyway. He accepts that the transitional code the approach needs has a cost, and says "the reduced risk and earlier value from the gradual approach outweigh its costs."
Three illustrations run through the lesson, each labelled:
- Minimal example: a small Java router that sends a share of traffic to the new system, with real output.
- Realistic example: extracting stock reservation from the ShopStream monolith.
- Failure example: two writers for the same data during a rollback.
The three phases
AWS Prescriptive Guidance names the steps transform, coexist, eliminate: build modernised components alongside the legacy system; keep the monolith for rollback while an HTTP proxy at its perimeter redirects traffic to the new version; then retire the old functionality as traffic moves away. Microsoft describes four stages of the same journey: a facade is introduced and routes most requests to legacy; it shifts more to the new system each iteration; once nothing depends on the legacy system it is decommissioned; and finally the facade is removed and clients talk to the new system directly.
Figure 1. The structure of the pattern. How to read this: clients keep one address. The facade decides per request which side answers. The bridge is the temporary integration between old and new, discussed below.
Figure 2. The loop repeats per slice. How to read this: each slice goes through the whole loop before the next one starts. The dotted arrow is the safety property: until deletion, rollback is a routing change.
Finding seams and choosing the first slice
A seam is a place where the system can be cut with little entanglement: a module with a narrow interface, a distinct set of URLs, a table only one feature uses. Fowler notes that well-designed systems would already have seams but are rare, and advises starting with small components, which carry less risk and return value sooner. Microsoft says to start after identifying new service boundaries (see domain-driven design and bounded contexts) and notes the approach lets you do "high-ROI replacements before low-ROI replacements."
Good first slices share three traits: clear inputs and outputs, a real reason to move (scaling, a blocked team, a volatile area), and little shared state. The tangled core usually comes last.
The pattern also has a precondition. Microsoft says it may not suit you when requests to the back end cannot be intercepted, when you cannot change the legacy system or any integration point such as its database, when the system is small enough to replace outright, or when you must decommission the original quickly.
The facade: routing and ramping
The facade is a proxy or gateway (AWS suggests an HTTP proxy such as Amazon API Gateway; any reverse proxy fits) that picks a side for each request. Microsoft warns that it must keep up with the migration and must not become a single point of failure or a performance bottleneck, so keep it thin: routing only, no business logic.
Minimal example. The router below matches a path prefix, then uses a stable hash of the user id to put a fixed share of users on the new system. A stable hash matters: the same user always lands on the same side, so no one flips between two behaviours on successive clicks.
record Rule(String pathPrefix, int percentNew) {}
static Target route(String path, String userId, List<Rule> rules) {
for (Rule rule : rules) {
if (path.startsWith(rule.pathPrefix())) {
int bucket = Math.floorMod(userId.hashCode(), 100);
return bucket < rule.percentNew() ? Target.NEW : Target.LEGACY;
}
}
return Target.LEGACY; // not listed: stays on the legacy system
}
Run with java StranglerRouter.java, with /catalogue at 100 percent, /stock at 20 percent and ten users u1 to u10. Output captured from a real run (Java 26.0.1, 2026-11-08), identical on a second run:
/catalogue/items NNNNNNNNNN new=10 of 10
/stock/reserve LLLLLLLLLN new=1 of 10
/orders/42 LLLLLLLLLL new=0 of 10
u3 on /stock/reserve, asked twice: LEGACY, LEGACY
Reading it: /catalogue is fully migrated, /orders still goes wholly to legacy because no rule lists it (the safe default), and /stock shows a 20 percent target landing on one of ten users. Ten users are too few to show the share; it is a hash of ten ids, not a measurement. The sample leaves out health checks, per-cohort rules and configuration reload.
Shadowing. Before ramping real answers, many teams also send a copy of read traffic to the new system and compare its responses with legacy, discarding the new response. This is common practice, not a step either vendor page prescribes, and it applies to reads only: a shadowed write would perform the action twice. Track the mismatch count, and treat each mismatch as either a bug in the new system or an undocumented behaviour of the old one.
Data: the hard part
Routing is easy to reverse; data is not. Microsoft's database example moves one domain at a time out of a shared monolithic database:
- The new service handles the domain's requests but still reads and writes the legacy tables.
- A separate domain database is created. An ETL load copies history, and change data capture (CDC) keeps it in sync from the legacy database. The legacy system keeps writing to the legacy tables; the new system writes to the domain database. Consistency is validated before cutover.
- After validation, the domain database is the system of record. The legacy tables, procedures and sync processes for that domain are removed.
Rollback is possible through phase 2 and at the start of phase 3, while the tables and sync still exist. After they are removed it "significantly increases effort and risk", so Microsoft says to treat removal as a deliberate final step per domain.
Figure 3. A database strangler. How to read this: step 1 uses the legacy tables, step 2 adds a synchronised, validated copy, step 3 switches the new service to the copy. The legacy side is cleaned only after validation.
A design rule, ours rather than Microsoft's, follows from the failure example below: for any given row, exactly one side is the writer at any moment. Reads can be duplicated across both sides; writes cannot.
Coexistence: the anti-corruption layer
While both systems run, the new one may call unmigrated legacy functions, and the legacy one may call migrated ones. Microsoft recommends an anti-corruption layer (ACL), an adapter that translates between the two models so the new design is not bent into the legacy one's shape. It describes the costs plainly: added latency, one more service to run and scale, and a decision about whether the layer is permanent or retired after migration. It also advises keeping the layer to translation only, with no business rules or orchestration, and validating input at the boundary.
Where legacy code cannot be changed, Microsoft notes the database can feed new services: a trigger that sends messages, or CDC reading the transaction log.
When it does not fit
Figure 4. The preconditions from Microsoft's guidance as a decision. How to read this: any "no" at the interception or change questions rules the pattern out; a small system or a short deadline does too.
| Concern | Strangler fig | Big-bang rewrite |
|---|---|---|
| Risk per step | Small, reversible until deletion | Concentrated at cut-over |
| Value delivered | Early, slice by slice | At the end |
| Cost | Transitional facade, bridge, two systems running | One system, but feature work usually stalls |
| Rollback | A routing change | Often not possible |
| Main failure | Never finishing | Never finishing, or finishing wrong |
The other side of this topic
On the front end, the same idea migrates pages or components to a new framework or to micro-frontends behind one shell. That is a planned lesson in the Frontend System Design track, not yet published, so there is no link.
In practice: extracting stock reservation from the ShopStream monolith
This is the realistic example on ShopStream, an educational reference design. No new numbers are used beyond the ramp percentages of Stage 8; the scenario is illustrative.
This is a realistic scenario, not a case history.
The situation
Checkout in the monolith reserves stock with a conditional update inside the same database transaction that creates the order. Inventory changes now ship on a different cadence from orders, and a store feed import slows checkout. The team wants Inventory as its own service, following the order in Stage 8.
Starting architecture
Figure 5. The starting point. How to read this: order creation and stock decrement commit together. Splitting them gives up that single transaction, which is the real cost of this extraction.
Options considered
| Option | Gain | Cost | Risk |
|---|---|---|---|
| A. Rewrite checkout and inventory together | One design | Months without releases | Fowler's "go down in flames" case |
| B. Extract reads first, then writes | Reversible steps | CDC feed, two phases | Slow |
| C. Extract writes first | Fastest to the goal | Distributed reservation at once | Overselling during the switch |
The decision
Option B, because it follows the data steps above. Phase one: the new Inventory service serves availability reads from a copy kept in sync by CDC; the monolith remains the only writer. Phase two: the monolith's checkout calls a reservation endpoint on the new service through an ACL, with an idempotency key, and writes move one group of stores at a time. The old tables go last.
Rollout and verification
Shadow the reads and compare answers, then ramp 1, 10, 50 and 100 percent at the facade, as in Stage 8, watching error rate, p99 latency and mismatch count. Before moving a store group's writes, compare per-SKU quantities between the two databases and require zero differences. Keep the legacy tables until the group has run a full release cycle.
The 2 a.m. failure
The page reads: oversold units on three stores. A deploy rolled the facade back for one store group, so checkout wrote stock to the monolith again while the new service had already accepted reservations for that group. Two writers, two truths. Figure 6 shows the sequence.
Figure 6. A routing rollback is not a data rollback. How to read this: the router moved back, the data did not. Both sides acknowledged the same last unit.
The first check is a per-SKU diff between the databases. The mitigation is to freeze reservations for the group, treat the new database as the truth for post-cutover reservations, and apologise or substitute for the oversold units. The lasting fix is to enforce the single-writer rule in the data layer: after cutover the monolith's stock write path rejects that group, so a routing rollback fails loudly instead of writing.
Architect's notes
- Make rollback a routing change until the moment of deletion, and rehearse it.
- One writer per piece of data at all times, enforced in code, not only in the router.
- Keep the facade thin and deploy it independently.
- Count the transitional cost: facade, bridge, CDC, two systems.
- Set an end date per slice, with deletion in the definition of done.
- Fowler's last advice: change the organisation too, or the new system "will end up in a similar mess".
Common mistakes
- Leaving the old code in place after the traffic has moved, so two implementations survive.
- Moving reads and writes at once.
- Putting business rules in the ACL or the facade.
- Starting with the most tangled core.
- Treating the facade as free: it can be a single point of failure.
- Removing legacy tables before the validation window ends.
Key takeaways
- Strangler fig replaces a system slice by slice behind a facade, in three phases: transform, coexist, eliminate.
- It needs interceptable requests and a legacy system you can change or integrate with.
- Start with small, well-bounded slices; the tangled core comes last.
- Ramp by stable hash so users do not flip between systems; rollback is a routing change.
- Data is the hard part: copy, sync with CDC, validate, then cut over; one writer at a time.
- An ACL protects the new design; keep it translation-only and decide whether it is retired.
- A migration is finished only when the old code is deleted.
Practice exercises
Exercise 1 (30 min). Extend the router with a header that pins an internal tester to the new system and a rule that overrides the hash. State the order of precedence.
Exercise 2 (45 min). List five seams in a system you know and rank them by Fowler's and Microsoft's criteria for a first slice.
Exercise 3 (1 hour). Write the validation query that compares per-SKU stock between the legacy and domain databases and the pass condition for cutover.
Exercise 4 (1 hour). Design the ACL calls between the monolith's checkout and the new reservation endpoint. What does it translate, and what must it not do?
Exercise 5 (2 hours). Plan the retirement: the checklist for deleting the facade, the bridge and the legacy tables for one slice.
Related reading
- Architecture styles: monolith, modular monolith, microservices.
- Microservices decomposition and data ownership: where the seams are.
- ShopStream Stage 8: Services: the gateway-first sequence.
- Distributed transactions: what replaces the single transaction.
- API gateway and service mesh: the facade in practice.
Official documentation
- Martin Fowler, Strangler Fig.
- Microsoft Azure Architecture Center: Strangler Fig pattern, Anti-corruption Layer pattern.
- AWS Prescriptive Guidance, Strangler fig pattern (Decomposing monoliths into microservices).