When Should You Actually Break Your Spring Boot App into Microservices
by Eric Hanson, Backend Developer at Clean Systems Consulting
The Wrong Reason Is Usually the First One
The most common reason teams break out a microservice is that someone read something. A conference talk, a Netflix engineering blog post, a job posting that listed microservices under requirements. The architecture gets adopted as an aspiration before there's a concrete problem it's solving.
This matters because microservices are expensive. Each extracted service adds a deployment pipeline, an observability surface, a network boundary, and an on-call surface that didn't exist before. If you can't name the specific problem that extraction solves — in terms of your actual system, your actual team, your actual traffic — you're paying that cost for nothing.
The decision framework isn't complicated. It has four legitimate triggers. If none of them are present, you don't have a reason to extract — you have a preference.
Trigger 1: Scaling Requirements Have Diverged
This is the cleanest technical justification. If one component of your application needs to scale independently from the rest — different resource profile, different scaling curve, different hardware — bundling it in the monolith forces a bad compromise.
The classic case is a video transcoding or image processing pipeline sitting inside a web application. The web layer needs low latency and horizontal scaling across many small instances. The processing pipeline needs high CPU, runs long, and scales by queue depth, not request rate. Forcing those into a single deployment artifact means either over-provisioning the web nodes or starving the processing jobs.
Before extracting, ask whether the divergence is real or projected. If your processing pipeline handles 200 jobs per day and your web tier serves 10,000 requests per day, the difference in load is real but the cost of running separate fleets may not yet be justified. The right question is: what does it cost to run a single instance of the processing component alongside the web tier, versus the operational cost of running it separately? Do that math with actual numbers before extracting.
When the numbers are concrete and the scaling profiles genuinely conflict, extraction is justified. Not before.
Trigger 2: A Team Boundary Demands It
Conway's Law is precise: the architecture of a system mirrors the communication structure of the team that builds it. The corollary is that you can use this intentionally — align service boundaries with team ownership and you get real autonomy. Misalign them and you get coordination overhead dressed up as architecture.
The signal that a team boundary is real: two groups of engineers would benefit from being able to deploy, test, and release their code without coordinating with each other. The signal that it's not real: one team owns the "service" but routinely needs sign-off from the other team to change the API contract.
Shared ownership of a service boundary is not ownership. If two teams are both modifying the same service or the same shared library that bridges services, the boundary isn't working. Extract when the boundary enables genuine autonomy — when it reduces the number of people who need to be in the same room (or Slack channel) to ship a change.
For a Spring Boot monolith with a team of 12, the right approach before extracting is often to enforce the boundary inside the monolith first. Use explicit package structure and ArchUnit rules to block cross-domain dependencies:
@AnalyzeClasses(packages = "com.example")
public class ArchitectureTest {
@ArchTest
static final ArchRule paymentDoesNotDependOnShipping =
noClasses()
.that().resideInAPackage("..payment..")
.should().dependOnClassesThat()
.resideInAPackage("..shipping..");
}
If the team can maintain that rule cleanly for six months, extraction becomes straightforward — the seam is already well-defined. If the rule keeps getting violated, you don't have a clean domain boundary yet, and extraction will just move the coupling across a network call.
Trigger 3: Compliance or Security Isolation Is Required
Some regulatory requirements are easier — or only possible — to satisfy with hard process boundaries. PCI DSS scope reduction is the most common example: if your payment processing logic handles raw card data, isolating it in a separate service with its own network segment, its own audit logs, and its own access controls reduces the PCI scope of the rest of your system dramatically.
Data residency requirements can create similar pressure. If user data for EU customers must stay within EU infrastructure but your system serves global traffic, a service boundary lets you route and store data independently per region without making the entire monolith region-aware.
These are legitimate non-negotiable drivers. When a compliance requirement dictates a process boundary, you extract regardless of team size or traffic volume. The cost is justified by the regulatory risk it eliminates.
Trigger 4: The Monolith's Deploy Cycle Is Blocking You
If a change to one part of the system requires deploying the entire application, and that deployment takes 20 minutes, runs a 40-minute test suite, and requires a coordinated release window — that is a real productivity cost. It compounds daily.
The honest question is whether the bottleneck is actually the monolith or the process around it. A monolith with a fast test suite, trunk-based development, and automated deployment can deploy multiple times per day without microservices. Many teams have deploy friction because of process and test suite quality, not because of architecture. Extracting a service doesn't fix a 40-minute test suite — it just means you have a 40-minute test suite per service instead of one shared one.
If you've addressed the process and the deployment coupling is still real — a database migration in the billing module causes downtime for the notifications module, or a dependency upgrade in one domain forces a coordinated release across unrelated features — then the monolith boundary is genuinely costing you. Extract the component whose deploy cycle is independent and whose release cadence is different from the rest.
The Extraction Pattern That Minimizes Risk
When one of the four triggers is present and the decision is made, extract incrementally. Don't rewrite — strangle.
The strangler fig pattern applied to a Spring Boot monolith: the new service starts by proxying through the monolith. Traffic is routed to the new service via an API gateway or load balancer rule, while the monolith remains authoritative for the data. You migrate reads first, then writes, then cut over the data ownership and retire the monolith code path.
Client → API Gateway
├── /payments/** → PaymentService (new)
└── /** → Monolith (existing)
At each step, the monolith is still running and can be fallen back to. This matters because extracted services almost always surface integration assumptions that weren't visible inside the monolith — data access patterns that don't translate cleanly across a service boundary, transaction semantics that depended on shared database state, caching behavior that assumed shared memory.
Discover those assumptions with real traffic at low blast radius, not in a big-bang cutover.
The Default Position
If you're reading this because you're planning an extraction and looking for validation — the framework is the answer. Run through the four triggers with your actual system. If you have a concrete instance of one of them, the extraction is defensible. If you're pattern-matching to justify a decision already made, that's the signal to slow down.
Microservices extracted for the right reason, at the right time, with the right team, work well. The monolith you understand completely beats the distributed system you don't — every time.