Spring Modulith
Das Framework, das Module in Spring Boot erstklassig macht: Anwendungsmodule deklarieren, Grenzen in einem Test verifizieren, Modul-Events und automatisch erzeugte Dokumentation.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
Everything so far - feature modules, enforced boundaries, API-and-event communication - can be done by hand with package conventions and ArchUnit. Spring Modulith is a Spring project that makes all of it first-class and Spring-native: it defines what a module is by convention, verifies the boundaries in a one-line test, gives you reliable module events, and even generates documentation. It's the framework encoding of this entire module.
Application modules by convention
Spring Modulith treats each direct sub-package of your main application package as an application module, with a simple visibility rule: the module's top-level types are its public API, and anything in sub-packages is internal:
com.ledger ← application package
├── LedgerApplication.java
├── billing/ ← an application module
│ ├── BillingApi.java ← top-level = PUBLIC (other modules may use)
│ ├── Invoice.java ← top-level = PUBLIC
│ └── internal/ ← sub-package = INTERNAL (hidden from other modules)
│ └── InvoiceRepository.java
└── orders/ ← another application module
└── ...No module-info.java, no manual ArchUnit rules for the basic case - the convention is the boundary: other
modules may reference billing's top-level types but not anything under billing.internal. You can refine
visibility with @ApplicationModule (e.g. declaring allowed dependencies or named interfaces).
Verifying boundaries in one test
The killer feature: a single test verifies the entire module structure - no cross-module access to internals, no cyclic dependencies between modules:
class ModularityTests {
ApplicationModules modules = ApplicationModules.of(LedgerApplication.class);
@Test
void verifiesModularStructure() {
modules.verify(); // fails if any module touches another's internals or a cycle exists
}
}That one line replaces a stack of hand-written ArchUnit rules and fails the build the moment someone violates a boundary. It's the enforcement mechanism from two lessons ago, built in.
Module events, made reliable
Spring Modulith builds on Spring's ApplicationEventPublisher but adds what production needs.
@ApplicationModuleListener marks a listener that runs asynchronously and transactionally (in its own
transaction, after the publisher's commits):
@Component
class Fulfillment {
@ApplicationModuleListener // async + transactional module event handling
void on(InvoicePaid event) { beginShipment(event.orderId()); }
}Crucially, Modulith offers an event publication registry: it persists each event before delivery and marks it completed after the listener succeeds, so an event isn't lost if the app crashes mid-handling, and incomplete events can be republished on restart. That's the transactional outbox pattern (from the DDD domain-events lesson) provided out of the box - the reliability layer that turns fire-and-forget events into something you can trust between modules.
Documentation and observability for free
Because Modulith understands your modules, it can generate documentation from them: Documenter produces
PlantUML/C4 component diagrams of your modules and their dependencies, plus per-module "canvas" summaries - always
in sync with the code, not a stale wiki page. It also integrates with Micrometer/Actuator to observe
module-to-module interactions. The architecture becomes visible and self-describing.
Modulith makes the modular monolith the path of least resistance
The value isn't any single feature - it's that Spring Modulith removes the friction. Modules are a convention (not manual config), boundaries are verified in one test (not a bespoke ArchUnit suite), events are reliable by default (not hand-rolled outboxes), and docs generate themselves. When doing the modular thing is the easy thing, teams actually keep the modularity - which is the whole battle. It's also the smoothest on-ramp to later extracting a module into a service, since it already nudges you toward clean APIs and event-based communication.
Doing modular monolith by hand is like driving a mountain road where you personally have to remember every cliff edge and paint your own lane markings (manual conventions and ArchUnit rules) - possible, but one lapse and you're over the edge. Spring Modulith is the road built with guardrails, painted lanes, and mile markers already installed: the safe path is the obvious one, a single inspection (modules.verify()) confirms the whole route is sound, and the map (generated docs) updates itself as the road changes. You still drive, but the infrastructure makes staying on the road the natural thing to do rather than a feat of constant vigilance.
A team adds Spring Modulith to the ledger with a single modules.verify() test. Two changes land in the same week: (a) a developer in the reporting module writes import com.ledger.billing.internal.InvoiceRepository; (b) orders calls billing and billing now calls back into orders, forming a cycle. Explain what happens to the build for each, and what Modulith gives you beyond this verification that hand-rolled ArchUnit would not.
What does Spring Modulith's modules.verify() check, and what does its event registry add?
Key takeaways
- Spring Modulith treats each direct sub-package of the app package as a module: top-level types are public, sub-packages are internal - the boundary is a convention.
- A single modules.verify() test fails the build on cross-module internal access or cyclic module dependencies - built-in enforcement.
- @ApplicationModuleListener handles module events asynchronously and transactionally, decoupling publisher from consumer.
- The event publication registry persists events and republishes incomplete ones after a crash - the transactional outbox pattern out of the box.
- Modulith generates C4/PlantUML documentation and canvases from the code and adds observability - making modularity the path of least resistance and easing later service extraction.