The 30 second answer
I choose based on deployment and ownership needs, not traffic alone. A modular monolith keeps calls local and transactions simpler, which is often a good fit for a small team or changing domain. Microservices help when teams need independent releases or a capability needs independent scaling. But they add network failures, cross-service consistency work, and operational overhead, so the benefits need to justify those costs.
A monolith is deployed as one application unit; it need not be one unstructured codebase. A modular monolith can enforce clear business boundaries while keeping interactions in-process. Microservices turn selected boundaries into independently deployed services, usually aligned with business capabilities and owning their data. The architectural distinction is independent deployment and autonomy, not simply repository count, container count, or line count.
Decision framework (editorial synthesis of the documented tradeoffs):
- Prefer a modular monolith when the domain boundaries are still changing, one team owns the application, and independent releases or scaling have little demonstrated value. Local calls avoid network round trips, and a business operation can use a local database transaction when its data is in that transaction's scope.
- Consider microservices when business capabilities have stable ownership, teams are blocked by shared releases, or a particular capability has materially different scaling or availability needs. Services must actually be independently deployable; splitting code while requiring coordinated releases creates a distributed monolith rather than useful autonomy.
- Budget for service discovery, deployment automation, contract evolution, distributed tracing, timeouts, and data consistency. Calls can time out after a remote service has already committed, so retries need idempotency. Multi-service business workflows cannot simply reuse an in-process database transaction.
These benefits and challenges are described in the official architecture guidance and its tradeoff analysis.
For example, an early commerce application can keep catalog, checkout, and billing as modules in one Spring Boot deployment. If catalog search later needs a separately owned release cadence and much heavier read capacity, extracting that capability may be justified while checkout and billing remain together. This is an illustrative decision, not a universal rule that search must be a service. Do not split every entity into a service: domain boundaries and cohesive business capabilities matter more than producing tiny services.
Neither style guarantees resilience. A monolith can run multiple replicas behind a load balancer; microservices can propagate failures through synchronous call chains unless callers isolate faults. Likewise, millions of requests do not by themselves dictate microservices: capacity depends on workload, storage, caching, and measured bottlenecks.