Microservices

How to Migrate a Monolith to Microservices, Step by Step

How to Migrate a Monolith to Microservices, Step by Step — cover illustration

Almost every team that decides to "rewrite the monolith as microservices" in one shot regrets it. The big-bang rewrite means months with no shippable value, a moving target as the old system keeps changing, and enormous risk at cutover. There's a better way — the strangler-fig pattern, where you grow the new system around the old one and retire the monolith piece by piece while it keeps serving traffic.

Strangler-fig migration path: from a monolith, map bounded contexts, extract one service, route through an API gateway, and decouple with Kafka events
Extract one capability at a time behind a gateway — the monolith keeps running throughout.

Step 1 — Map the domain before you cut

You can't extract services cleanly from a structure you don't understand. Start by mapping bounded contexts — the natural business capabilities inside the monolith (ordering, catalogue, payments, notifications). Look for clusters of classes and tables that change together and rarely touch the rest. These seams are your future service boundaries. Resist the urge to split by technical layer; split by capability.

Step 2 — Put a gateway in front

Introduce an API gateway (or reverse proxy) that all traffic flows through, still routing everything to the monolith for now. This single seam is what makes incremental extraction possible: later, you redirect one route at a time to a new service, and clients never know the difference.

Step 3 — Extract the first service (choose wisely)

Your first extraction should be low-risk and high-learning. Good candidates are capabilities that are relatively self-contained and read-heavy — notifications, search, reporting. Avoid starting with the most tangled, transaction-heavy core. For the chosen capability:

  • Build the new Spring Boot service with its own database.
  • Migrate or replicate just the data it owns.
  • Flip the gateway route for that capability to the new service.
  • Keep the old code path behind a feature flag so you can roll back instantly.

Step 4 — Break the data dependencies

This is the hardest part. In a monolith, modules share tables and join freely. A service can't reach into another service's database, so you replace those joins with API calls or, better, with events. When the monolith updates an order, it emits an OrderUpdated event; the new service maintains its own view from that stream. The transactional outbox pattern ensures the event fires exactly when the data commits.

// Inside the monolith's transaction:
orderRepository.save(order);
outbox.add(new OrderUpdated(order.id(), order.status())); // same DB tx
// A relay process publishes outbox rows to Kafka after commit.

Step 5 — Repeat, measure, and retire

Extract the next capability, then the next. Each iteration ships independently and de-risks the whole. Track concrete signals so you know it's working: deployment frequency per service, lead time for changes, and incident blast radius (a bug now affects one service, not everything). When the last meaningful capability has moved out, the monolith shrinks to a husk you can finally delete.

Common pitfalls

  • Extracting too much at once — one capability per iteration, always.
  • Shared databases between new services — that's a distributed monolith.
  • No automated tests around the seams — characterization tests catch regressions during extraction.
  • Skipping observability — you need tracing the moment a request spans two systems.

Key takeaways

  • Never do a big-bang rewrite — use the strangler-fig pattern to migrate incrementally.
  • Map bounded contexts first, route through a gateway, and extract one capability at a time.
  • Replace shared-database joins with events and the outbox pattern; give every service its own data.

We modernise legacy Java monoliths into clean, independently deployable services. See our case studies or talk to an architect about your migration.

Keep reading

Related articles

Need this built, not just blogged?

We engineer Java, Spring Boot and cloud-native systems for a living. Let's talk.

Talk to an architect