The strangler fig pattern replaces a legacy system one piece at a time: a routing layer goes in front of the old application, each piece of functionality moves behind it to new code, and the old system keeps running until nothing sends it traffic. The code is rarely the hard part. The shared database and the long middle of the migration are where these projects stall, and both can be planned for from the first week.

What is the strangler fig pattern?

The strangler fig pattern is an incremental way to modernize a legacy application. Instead of replacing the whole system in one cut-over, a team builds new code next to the old system and puts an interception point in front of both, which decides for each request whether the old or the new implementation serves it. Functionality moves to the new side piece by piece, and the old system is retired once nothing depends on it. Microsoft and AWS both describe it as a standard pattern for migrating legacy systems (Azure Architecture Center, AWS Prescriptive Guidance).

Martin Fowler named the pattern after the strangler figs he saw in Australian rainforests. The fig starts in the branches of a host tree and grows down to the ground, until it can stand on its own and the host is no longer needed.

He first called it the strangler application and later changed the name to put the fig back in, which is why both names are in use.

How to start: a routing layer in front of the legacy system

Before anything is rewritten, every request to the old system has to pass through something you control. That facade can be an API gateway, a reverse proxy such as nginx, or, inside a monolith, a routing module that decides which implementation serves a call.

On day one it sends everything to the old application and changes nothing for users.

The facade is what makes the rest of the migration reversible. When a new piece misbehaves in production, routing its traffic back to the old code is a configuration change, not a deployment of the old code, and that option is what lets a team ship the first slice with confidence.

The strangler fig pattern in three stages: a facade routes all traffic to the legacy application; then one slice, /orders, goes to a new service while the rest stays on legacy; finally the legacy application is retired.1. Route everythingFacadeall requestsLegacy application2. Move one sliceFacade/ordersthe restOrdersLegacy3. Retire the old codeFacadeall requestsNew serviceslegacy switched off
The strangler fig pattern in three stages. The facade stays in place throughout; only what sits behind it changes.

Choosing the first slice to migrate

Teams often want to start with the part of the system that hurts most. It is usually the worst choice for a first slice, because the part that hurts is usually the part that everything else depends on.

A good first slice proves the routing, the deployment pipeline and the monitoring on something that can fail without consequences.

A good first sliceA poor first slice
Mostly reads, few writesWrites to tables other features also write to
Few calls into the rest of the systemShares transactions with other modules
Changes often, so new code pays off quicklyRarely changes, so nobody notices the improvement
Has one owner who can accept the resultBilling, payments or anything with regulatory reporting

Search, reporting screens and read-only APIs are typical first slices. They exercise every part of the new setup while the old system keeps doing the writes. If the monolith has no clear modules yet, enforce them first: our guide to monolith to microservices migration covers the modular monolith step and how to find the seams.

Strangler fig pattern example with Spring Cloud Gateway

For a web application, the facade can be a small Spring Cloud Gateway service. The configuration below sends /orders/** to a new orders service and everything else to the legacy application. Routes are matched in order, so the catch-all goes last. On Spring Cloud 2025.0 and later the routes sit under spring.cloud.gateway.server.webflux; older versions use spring.cloud.gateway.routes.

spring:
  cloud:
    gateway:
      server:
        webflux:
          routes:
            - id: orders-new
              uri: http://orders-service:8080
              predicates:
                - Path=/orders/**
            - id: legacy
              uri: http://legacy-app:8080
              predicates:
                - Path=/**

Shifting traffic gradually with weighted routes

Cutting over in one go is rarely necessary.

The Weight predicate splits traffic between two routes in the same group, here ten percent to the new service and ninety to the old one, so the new code meets real traffic before it has to carry all of it.

Raising the share is a one-line change.

            - id: orders-new
              uri: http://orders-service:8080
              predicates:
                - Path=/orders/**
                - Weight=orders, 10
            - id: orders-legacy
              uri: http://legacy-app:8080
              predicates:
                - Path=/orders/**
                - Weight=orders, 90

Branch by abstraction inside a monolith

Inside a monolith, where there is no network hop to intercept, the same idea is called branch by abstraction. The calling code depends on an interface, both the old and the new implementation sit behind it, and a flag decides which one serves the call:

public interface OrderLookup {
    Order find(OrderId id);
}

@Bean
OrderLookup orderLookup(LegacyOrderRepository legacy, OrdersClient orders,
                        @Value("${orders.use-new-service:false}") boolean useNew) {
    return useNew ? orders::find : legacy::find;
}

Once the new implementation has carried all the traffic for long enough, the flag, the old implementation and eventually the interface are deleted.

That deletion is the part teams forget to schedule.

Handling the shared database in a strangler migration

Routing requests is straightforward. Most legacy systems, though, keep everything in one database, and the new code needs the same data.

There are four ways to handle it, and the choice matters more than any framework decision.

  • Keep the old database as the system of record and let the new code read from it, or from a store kept in sync with it. It is the lowest-risk start, and often enough for read-heavy slices.
  • Stream changes into the new store with change data capture, for example Debezium publishing row changes to Kafka. The new service gets its own copy without the old code knowing it exists.
  • Avoid dual writes from the application. Writing to both databases in the same request looks simple and breaks the first time one write succeeds and the other fails. There is no transaction across the two, so they drift apart silently.
  • Put an anti-corruption layer between the new code and the old data model, so the new service is designed around the business rather than around column names from years ago.

Example: a hotel metasearch engine

Our work on a UK hotel metasearch engine followed the first two ideas. Availability search moved out of the relational database into an in-memory data grid, while the database remained the system of record.

The engine went on to serve 300 million queries a day at 80 percent lower infrastructure cost. The case study has the details.

Case study: a UK hotel metasearch engine. Availability search moved out of SQL Server into an in-memory data grid, fed by a fast loader, while SQL Server stayed the system of record. Results: 300 million queries a day, 80% lower infrastructure cost and 60% lower response time.CASE STUDY: TRAVELUK hotel metasearch engineAvailability search moved out of SQL Server intoan in-memory data grid; the database stayed thesystem of record.300 millionqueries a day80% lowerinfrastructure cost60% lowerresponse timeSearch requestsIn-memory data gridSQL Serversystem of recordfast loaderRead the case study

Keeping a strangler migration from stalling in the long middle

The first slice usually goes well. The trouble starts at slice five or six, when the easy parts are done, the old system still runs everything important, and the team is maintaining two systems at once.

Migrations that stall there leave the company worse off than before: twice the infrastructure and twice the knowledge to keep.

  • Delete as you go. The old code for a slice is removed in the same iteration its replacement takes full traffic, not in a clean-up phase at the end.
  • Track one number: the share of requests served by new code. It should rise every month; when it stops rising, the migration has stalled.
  • Give every slice an end date, agreed before work on it starts.
  • Treat the facade as a product. It needs an owner, monitoring and a plan for its own retirement, or it becomes the next legacy component.

When not to use the strangler fig pattern

The pattern suits large systems that have to keep running while their architecture changes. For many systems it is more machinery than the problem needs, and our guide to legacy system modernization compares it with the other options, from keeping a system as it is to replacing it.

  • The application can be upgraded in place. A small or mid-sized system on old versions often needs an upgrade, not a new architecture. We moved a fifteen-year-old online store and ERP from Java 17 to Java 25 in about four weeks without splitting it (see the case study). With the Java support deadlines arriving between 2029 and 2033, that is the more common job, and our guide to application modernization shows how to decide across a whole portfolio.
  • The data model is the problem. If the tables themselves are wrong, moving code around them changes little. The data needs its own migration plan first.
  • The calls cannot be intercepted. Nightly batch jobs, shared files and direct database access by other systems bypass any facade, and each needs a separate plan.
  • Nobody can run two systems for a year. Without a team that can operate the old and the new side by side, a planned replacement with a fixed cut-over date can be the safer choice.

If you are deciding whether your system needs a strangler migration or a straight upgrade, that is what the first step of our Application Modernization Sprint answers: a written view of the options, the order of work and a ballpark estimate, within a week of a thirty-minute call.