Spring Modulith — what problem does it solve between mono… — Cracked Java
// Spring Framework & Spring Boot · Spring Boot 3 Features & Modern Stack
SeniorSystem Design

Spring Modulith — what problem does it solve between monolith and microservices?

Spring Modulith solves the "big ball of mud" problem of monoliths without forcing you into microservices. It lets you build a modular monolith: one deployable unit, but with enforced internal boundaries — so you keep operational simplicity while getting the architectural discipline people usually go distributed to obtain.

The problem in the middle

A plain monolith has no real boundaries: any class can call any other, so over time modules grow tangled dependencies and you can't reason about or extract any piece. Microservices give you boundaries — but at the price of network calls, distributed transactions, separate deployments, and operational overhead you may not need yet. Modulith targets the gap: logical modularity, physical monolith.

How it enforces boundaries

A module is, by convention, a top-level package under your application package. Code in a module's root package is public API; code in sub-packages is internal and other modules must not touch it.

// com.shop.order      <- module "order", public API
// com.shop.order.internal  <- internal, off-limits to other modules
// com.shop.inventory  <- separate module

You verify this with a test — it fails the build if a module reaches into another's internals:

@Test
void verifiesModularStructure() {
    ApplicationModules.of(ShopApplication.class).verify();
}

Communicating without coupling

Instead of one module directly calling another's beans, Modulith encourages application events for cross-module interaction, decoupling them at the API level. It adds an event publication registry: published events are persisted, so if a listener fails the event isn't lost and can be retried — giving you reliable, transactional-outbox-style messaging inside the monolith.

@Component
class OrderService {
    OrderService(ApplicationEventPublisher events) { /* ... */ }
    void place(Order o) { events.publishEvent(new OrderPlaced(o.id())); }
}
// In inventory module:
@ApplicationModuleListener   // async + transactional + persisted
void on(OrderPlaced e) { reserveStock(e.orderId()); }

It also documents itself — generating C4/PlantUML component diagrams and module canvases from the verified structure.

Mark your status