Enforce module boundaries at test time.
Modulith Rules is a set of pre-built ArchUnit rules for modular monoliths. It runs in your test phase, so nothing is added to the production classpath and the core module has no framework dependency.
Rules run during mvn test. Nothing ships to production, and the core module has no Spring dependency.
Boundaries, cycles and communication contracts in the core module; controllers, repositories, transactions, events and injection in the Spring module.
Declare modules in Java, in modulith-rules.yml, or let each base package be derived as <root>.<module>.
Add the dependency
Core module, test scope. Add modulith-rules-spring for the Spring rules, or import the BOM to align versions.
<dependency>
<groupId>io.modularmonolith</groupId>
<artifactId>modulith-rules-core</artifactId>
<version>0.2.0</version>
<scope>test</scope>
</dependency>
Write one test
The convention-based path: each module's base package is derived from the root package and the module name.
@AnalyzeClasses(packages = "com.example")
class ArchitectureTest {
@ArchTest
static final List<ArchRule> rules =
ModulithRules.forPackage("com.example",
"ordering", "payments", "inventory")
.allRules();
}
The rule catalogue
See the violation messages →allRules() returns three rules: allowed dependencies, internal protection, cycle detection. allCoreRules() adds API-only access and communication contracts. Both of the added rules skip modules that have not declared the relevant configuration, so the longer list is safe to start with.
Boundary rules
modulesOnlyDependOnAllowedModules()internalsShouldNotBeAccessedFromOutside()crossModuleAccessOnlyThroughApi()moduleShouldHaveNoDependencies(name)moduleShouldOnlyBeAccessedBy(name, ...)onlyAllowedDependenciesAreUsed()noModuleAccessesInternalsOfOthers()Cycle rules
noModuleCycles()moduleHasNoCycles(name)noCircularDependenciesBetweenModules()noModuleCycles()CycleRules.detectCycles(graph) is public and static, so you can run the DFS against a hand-built graph without importing any classes.Communication rules
asyncModulesShouldNotCallDirectly()noCommModulesShouldNotInteract()allCommunicationContractsRespected()Spring rules
modulith-rules-springcontrollersShouldNotCrossModuleBoundaries()repositoriesShouldBeModuleInternal()transactionalMethodsShouldNotSpanModules()eventClassesShouldBeInApiPackages()noDirectInjectionOfInternalBeans()Export the module graph
dependencyGraph().toMermaid(classes) renders the module graph as a Mermaid flowchart string, ready to paste into any Markdown file. Every registered module appears, including isolated ones.
Solid arrows are observed dependencies; an arrow renders dashed when the source module declares an ASYNCHRONOUS contract. Nodes sort by name and edges by source then target, so committing the diagram produces a clean diff.
flowchart LR
inventory
notifications
ordering
payments
notifications -.-> ordering
ordering --> inventory
How it differs from Spring Modulith
Spring Modulith is a runtime framework covering module verification, observability and event publication. Modulith Rules runs only at test time. You can use both: Spring Modulith for runtime isolation, Modulith Rules for build-time enforcement.
| Concern | Modulith Rules | Spring Modulith |
|---|---|---|
| When it runs | Test phase, during mvn test | Application runtime |
| Production classpath | Nothing added | Runtime dependency |
| Framework coupling | Core module has none | Spring Boot |
| Rule authoring | Rules ship ready to use | Verification model, custom checks written by hand |
| Non-Spring projects | Supported through the core module | Out of scope |
Common questions
Short answers to what people ask before adopting the library.
What is a modular monolith?
A single deployable application whose code is divided into modules with explicit boundaries. Each module owns its data and exposes a narrow API; other modules go through that API rather than reaching into internal classes. You get the independence of service boundaries without distributed transactions, network failure modes or separate deployment pipelines.
How do you enforce module boundaries in Java?
With an architecture test that fails the build when a boundary is crossed. Modulith Rules provides that test: declare your modules, then check the rules against your compiled classes with ArchUnit. Package-private visibility and Java modules help, but neither expresses allowed dependencies between modules or detects cycles.
Should I use Modulith Rules or Spring Modulith?
Use Spring Modulith when you want runtime module isolation, an event publication registry and observability in a Spring Boot application. Use Modulith Rules when you want build-time enforcement with nothing added to the production classpath, or when the project is not a Spring project. They run at different times and can be used together.
Does it work without Spring?
Yes. modulith-rules-core carries the boundary, cycle and communication rules and has no Spring dependency. The Spring rules live in a separate artifact you add only if you want them, and its Spring dependencies are provided scope.
Will it slow down my build?
The cost is ArchUnit's class import, which happens once per test class and is cached across rules in the same @AnalyzeClasses run. On a codebase of a few thousand classes the whole rule set adds a few seconds to the test phase.
How do I adopt it on an existing codebase?
Start with cycle detection alone, which needs no configuration beyond module names. Then declare allowedDependencies one module at a time: a module with none declared is left unrestricted, so you can tighten boundaries incrementally without a red build on day one.