Skip to content
Spring Boot sb microservices 5 min read

Monolith vs Microservices

Before reaching for Spring Cloud, you need to know what problem microservices actually solve. Monolith and microservices are two ends of an architecture spectrum, and the right choice depends on your team, your scale, and your tolerance for operational complexity. This page defines both, compares them honestly, and introduces the modular monolith as the pragmatic middle ground most teams should start from.

What is a monolith?

A monolithic application packages all functionality — web layer, business logic, data access — into a single deployable unit. In Spring Boot that is one executable JAR built from one codebase, running in one process, talking to (usually) one database.

┌─────────────────────────────────────────┐
│            orders-app.jar                 │
│  ┌─────────┐ ┌─────────┐ ┌────────────┐  │
│  │ Orders  │ │Inventory│ │  Payments  │  │
│  │ module  │ │ module  │ │   module   │  │
│  └─────────┘ └─────────┘ └────────────┘  │
│        in-process method calls            │
└──────────────────┬───────────────────────┘
                   │ JDBC
            ┌──────▼──────┐
            │  one schema │
            └─────────────┘

Calls between modules are ordinary Java method calls — fast, transactional, and type-safe. A single @Transactional boundary can span orders, inventory, and payments atomically.

What are microservices?

A microservices architecture splits the application into independently deployable services, each owning a bounded slice of the business and (critically) its own data. Services communicate over the network — HTTP/REST, gRPC, or messaging — never by reaching into each other’s database.

┌──────────┐   ┌──────────┐   ┌───────────┐
│  Orders  │   │Inventory │   │ Payments  │
│ service  │   │ service  │   │  service  │
└────┬─────┘   └────┬─────┘   └─────┬─────┘
     │ HTTP / events │              │
  ┌──▼──┐         ┌──▼──┐        ┌──▼──┐
  │ DB  │         │ DB  │        │ DB  │
  └─────┘         └─────┘        └─────┘

Each service can be written, deployed, scaled, and even rewritten in isolation. The trade-off: a method call becomes a network call, and a database transaction becomes a distributed coordination problem.

Side-by-side comparison

DimensionMonolithMicroservices
DeploymentOne artifact, one deployMany artifacts, independent pipelines
ScalingScale the whole app (vertical or replicas)Scale hot services independently
DataShared schema, ACID transactionsDatabase-per-service, eventual consistency
TeamOne codebase, coordination overhead growsTeams own services end-to-end (autonomy)
Tech diversityOne stackEach service can pick its stack
ComplexityCode complexity in one placeOperational + distributed-systems complexity
Failure isolationOne bug can take down everythingA failing service degrades only its area
LatencyIn-process calls (nanoseconds)Network hops (milliseconds) + retries
TestingEasy end-to-end in one processNeeds contract tests, integration envs
Local devRun one appRun/mocks for many services

Note: Microservices trade code complexity for operational complexity. You don’t remove complexity — you move it into the network, deployment, and observability.

Scaling, in practice

A monolith scales by running more identical copies behind a load balancer. That works well until one part of the app — say a CPU-heavy PDF renderer — dominates resource use, forcing you to over-provision the entire app to scale one feature.

Microservices let you scale only what’s hot:

Orders service:    12 replicas  (high traffic)
Inventory service:  3 replicas  (moderate)
Reporting service:  1 replica   (nightly batch)

This granularity is a genuine win — but only if your traffic profile is actually uneven enough to justify the overhead.

Data: the hard part

In a monolith a single transaction keeps orders, inventory, and payments consistent:

@Transactional
public Order placeOrder(OrderRequest req) {
    inventory.reserve(req.items());   // same DB, same tx
    Order order = orders.create(req);
    payments.charge(order);           // all-or-nothing
    return order;
}

Split those into three services with three databases and you can no longer wrap them in @Transactional. You must coordinate with the Saga Pattern and accept eventual consistency. This is the single biggest cost of microservices, and the most underestimated.

The modular monolith: the pragmatic middle ground

Most teams do not need microservices on day one. A modular monolith keeps the single-deployment simplicity while enforcing clean module boundaries inside the codebase — so that if you later need to extract a service, the seams already exist.

Principles:

  • Package by feature, not by layer. com.shop.orders, com.shop.inventory, com.shop.payments — each with its own controllers, services, and repositories.
  • No reaching across modules. Modules talk through published interfaces or Spring ApplicationEvents, not by autowiring each other’s internal beans.
  • Own your tables. Each module owns its tables; no foreign keys crossing module boundaries.
// orders module publishes an event instead of calling inventory directly
@Service
@RequiredArgsConstructor
public class OrderService {
    private final ApplicationEventPublisher events;

    @Transactional
    public Order placeOrder(OrderRequest req) {
        Order order = repository.save(Order.from(req));
        events.publishEvent(new OrderPlaced(order.getId(), req.items()));
        return order;
    }
}
// inventory module listens — a clean seam to later become a network boundary
@Component
class InventoryListener {
    @EventListener
    void on(OrderPlaced event) { /* reserve stock */ }
}

Spring Modulith (org.springframework.modulith) can verify these boundaries at build time and even publish events across a transactional outbox — making the eventual extraction to microservices low-risk.

Tip: Start with a well-modularized monolith. Extract a service only when a specific module has a concrete need: independent scaling, a separate team, or a different release cadence. “We might need it” is not a reason.

Choosing

If you have…Prefer
A new product, small team, unproven domainModular monolith
Clear bounded contexts + multiple teamsMicroservices for the high-friction parts
Wildly uneven scaling needsExtract the hot path to a service
A small team and a stable productStay a monolith

The next page, Why Microservices?, digs into the real drivers and costs so you can make this call deliberately.

Last updated June 13, 2026
Was this helpful?