Spring Modulith: package boundaries you can verify in a test
Modular applications with Spring Boot
At a glance
- What is it?
- Spring Modulith is a Spring Boot library that treats the sub-packages of your main package as application modules, then gives you three things that are usually manual: a verification step that fails when a module reaches into another, integration tests that run one module at a time, and generated documentation diagrams of the arrangement you ended up with. The whole model is enforced by conventions rather than by a framework annotation.
- Who is it for?
- Spring Modulith fits a Spring Boot team whose application has grown past a sensible module count and who want the boundaries enforced by something rather than by convention, especially one willing to add a verification test that fails the build on a forbidden dependency.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Java, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.
Star history
- spring-projects/spring-modulith1,210 β Β· +9
| Repository | 2026-10-20 | 2026-11-07 |
|---|---|---|
| spring-projects/spring-modulith | 1.2k | 1.2k |
9 Stars in the last 18 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
What it actually does: package layout plus a verification step
Spring Modulith allows developers to build well-structured Spring Boot applications and guides developers in finding and working with application modules driven by the domain.
The interesting thing about the design is that there is no annotation to put on a class. The module is a package arrangement, and the documentation makes that explicit. You create a Java package arrangement that puts business modules as direct sub-packages of the application's main package:
β‘ Example
ββ β‘ src/main/java
ββ β‘ example <1>
| ββ Application.java
ββ β‘ example.inventory <2>
| ββ β¦
ββ β‘ example.order <2>
ββ β¦The two callouts are the application root package and the application module packages. So example.inventory and example.order are modules, and example is the application. That is the entire mechanism, and it is a convention rather than a configuration.
The three capabilities that follow from it are what make the convention enforceable. It supports the verification of such modular arrangements, integration testing of individual modules, observing the application's behaviour on the module level, and creating documentation snippets based on the arrangement created.
So a typical adoption is: take a Spring Boot application, look at its package structure, decide which sub-packages are business modules, and then get three things you would otherwise assemble by hand. The first is a verification that the structure holds. The second is a way to run a test that exercises one module without booting the whole application. The third is documentation that reflects the modules as they actually are rather than as you remember them.
The primary language is Java, the licence is Apache-2.0, and the homepage is spring.io/projects/spring-modulith with reference documentation at docs.spring.io. The topics are architecture, modularity and spring-boot, which is the whole scope. The repository is not archived with the last push on 2026-10-10, and the recent releases are 2.2.0-M1 on 2026-08-26, 2.1.1 on the same day and 2.0.8 on the same day, so the project ships several maintenance lines in parallel and the newest one is a milestone.
The verification call, and the documentation it produces
The quickstart shows the whole thing in a single test class, and it is short enough to be the clearest description of what the library does.
class ApplicationTests {
@Test
void writeDocumentationSnippets() {
var modules = ApplicationModules.of(Application.class).verify(); <1>
new Documenter(modules) <2>
.writeModulesAsPlantUml()
.writeIndividualModulesAsPlantUml();
}
}The first call is ApplicationModules.of(Application.class), which builds the module model from the class, and the chained verify() is what the comment describes as creating the application module model and verifying its structure. The verification is the important part, and it is where the boundary becomes real.
The second part is new Documenter(modules), which renders the documentation snippets, described as Asciidoctor snippets covering component diagrams and the application module canvas, and the output goes to target/modulith-docs.
Putting both in one test is the design. The same object, the module model, is used to check the arrangement and to draw it. That means the documentation cannot drift from the code the way a hand-maintained diagram can, because the diagram is regenerated by the same model that the verification passes on. And because it is a test, a structural violation fails the build rather than waiting for someone to notice the diagram.
The PlantUML output is a practical choice rather than a cosmetic one. PlantUML renders from a text description, so the generated file is diffable in version control, and a reviewer sees a changed arrow when a dependency appears or disappears. An image would show the same thing but not in a form you can review line by line.
The build path for the documentation is also the same one the CI uses, which means the docs are regenerated on every build and the target/modulith-docs output is a build artefact rather than a committed file. That is the difference between documentation that is always true and documentation that was true when someone wrote it.
The verification model itself is the part the documentation calls the verification of such modular arrangements, and the details of what counts as a violation, which dependencies are allowed and which are not, are in the reference documentation under verification rather than in the README.
Module integration tests, and why they are a different kind of test
The third feature in the quickstart is module integration testing, and the arrangement it expects is worth looking at closely.
The test goes in a package that matches the module, in the test source tree:
β‘ Example
ββ β‘ src/test/java
ββ β‘ example.order
ββ OrderModuleIntegrationTests.javaAnd the test class uses a dedicated annotation:
@ApplicationModuleTests
class OrderModuleIntegrationTests {
@Test
void someTestMethod() { β¦ }
}The placement is the point. The test for a module lives inside that module's package in the test tree, which means it can only see what the module itself can see, because the test class is compiled with the same package visibility rules as the production code. A test in example.order cannot reach into example.inventory's package-private classes, so a test that only needs the order module's behaviour cannot accidentally depend on another module's internals.
That is the difference between this and a normal Spring Boot integration test. An ordinary @SpringBootTest boots the whole application context, and the application context contains every bean in every module, so the test can see everything and depends on everything. A module integration test boots only the module, and so it fails fast if that module has an accidental dependency on something outside it.
This gives you something a full context test cannot: a test that tells you the module is self-contained. If the order module's integration test passes, the order module genuinely does not need the rest of the application, and that is the property a modular monolith is supposed to have.
The annotation itself is a test template, and the ellipsis in someTestMethod is doing real work in the documentation, because it is telling you that you write the test method yourself and the annotation handles the module setup around it. The starter-test dependency, which is the test-scoped artefact you add to the pom, is what pulls in those APIs.
The benefit is that these tests are fast, and fast is what makes them worth running. A suite of module integration tests gives you a fast feedback loop on the structural question, while a single full application context test gives you the slow answer to whether the whole thing still works. Running both is a reasonable split, and the module tests are the ones that catch the regression early.
The repository layout supports the picture, with spring-modulith-integration-test/ and spring-modulith-test/ as separate modules, plus spring-modulith-junit/ and spring-modulith-moments/.
Module level observability and events, which is the part people adopt for
Verification and tests are the parts a reviewer sees. The observability side is what a team notices in production, and the module layout in the repository shows how much machinery is behind it.
The capability is described as observing the application's behaviour on the module level, and the repository has three separate modules for it. There is spring-modulith-observability/ for the core support, spring-modulith-actuator/ for the Spring Boot Actuator integration, and spring-modulith-moments/ for the event recording side, alongside spring-modulith-events/ for module event publication.
The pairing of observability and actuator is the standard Spring Boot arrangement, and the reason for a separate module is that the core library has to work without Actuator on the classpath, so the Actuator-specific parts live in their own artefact and are only present when you add it.
The MOMENTS module is the more interesting name and it connects to the events module. In a modular monolith, the question of how one module tells another something is a design question with several answers. Direct calls are coupling, a shared event bus is looser, and recording the events for later inspection is a third thing. MOMENTS is the recording side, and the presence of both an events module and a moments module suggests the project supports publishing module events and also capturing them for verification or analysis.
That matters for the verification story. If modules communicate by publishing events rather than by calling each other, then the dependency graph the verification checks is a different graph from the call graph, and a module can legitimately depend on another at runtime without a forbidden compile time dependency. Handling that distinction properly is more work than a simple package check, and it is the kind of thing that a project with both an events module and an observability module has had to think about.
The spring-modulith-runtime/ module is the other piece of that picture. A modular application in Spring Modulith can be a Spring Boot application with several modules, or it can be a multi-module arrangement where the modules are separate applications, and the runtime module is the support for the latter, where a module can be started and stopped independently. That is the point of the library in a larger context, the same code running as one process or as several, and the runtime module is what makes that possible.
So the full picture is: a package layout gives you modules, verification keeps them honest, module integration tests prove each one is self-contained, and the observability and events modules let you run the result as one process or as several while still seeing what each module is doing.
The dependency setup, the module map, and the build
The dependency configuration is a good example of how a Spring project does version management, and it has two parts rather than one.
The first is a BOM import, which is what keeps the many artefacts in this project on the same version:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-bom</artifactId>
<version>2.0.5</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>The second is the actual test dependency, which pulls in the verification APIs:
<dependencies>
<!-- The test dependency to pull in verification APIs -->
<dependency>
<groupId>org.springframework.modulith</groupId>
<artifactId>spring-modulith-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>Note that the BOM version in the example is 2.0.5 while the current release line is 2.0.8 and above, so the snippet is a version behind. That is a small detail but it is the kind of thing that makes a copy-paste start on an older patch, and the release train model means the patch level moves on its own schedule.
The module map in the repository listing shows how large the project is for what looks like a small idea. There is spring-modulith-core/ for the model and verification, spring-modulith-api/ and spring-modulith-apt/ for the API and the annotation processor, spring-modulith-observability/ and spring-modulith-actuator/ for the module level metrics, spring-modulith-events/ for the module event publication, spring-modulith-integration-test/ and spring-modulith-test/ and spring-modulith-junit/ for the test support, spring-modulith-moments/ for the event recording, spring-modulith-runtime/, spring-modulith-starters/, spring-modulith-bom/, spring-modulith-distribution/, spring-modulith-docs/, spring-modulith-examples/ and spring-modulith-benchmarks/.
Two of those are worth pausing on. The APT module is an annotation processor, which is the mechanism for the package conventions being checked at compile time rather than only at test time. And the MOMENTS module is the event recording side, which pairs with the events module and with the observability module.
The build for contributors needs Git, Java 17 or higher, and Docker version 1.6.0 or higher, and the build command is the Maven wrapper:
./mvnw -BThe repository has mvnw and mvnw.cmd, so the wrapper is committed and no local Maven install is needed, and there is a .mvn/ directory for the wrapper configuration.
Editorial conclusion
Spring Modulith fits a Spring Boot team whose application has grown past a sensible module count and who want the boundaries enforced by something rather than by convention, especially one willing to add a verification test that fails the build on a forbidden dependency. It is the wrong tool if you need enforced module isolation at runtime, since the verification is a build time check rather than a boundary the container enforces, and it is the wrong tool if you want framework-level module abstractions rather than package structure. Before adopting, run the verification on your existing codebase before changing any package names, because the first run tells you how many violations you already have, and read the module boundary documentation to decide which dependencies you actually want to forbid.
Frequently asked questions
What does Spring Modulith actually add to a Spring Boot application?
It treats the direct sub-packages of your main package as application modules, then provides verification of that arrangement, integration testing of individual modules, observing behaviour at the module level, and generated documentation snippets based on the arrangement. The module is a package layout rather than an annotation, so there is nothing to mark your classes with.
How do I verify my application module structure with Spring Modulith?
Build the module model and call verify on it, as in ApplicationModules.of(Application.class).verify(), which creates the model and verifies its structure. Putting that call in a test makes a structural violation fail the build rather than waiting for someone to notice.
What is the difference between an @ApplicationModuleTests test and a normal Spring Boot test?
An @ApplicationModuleTests class lives in the test tree inside the module's own package, and it boots only that module, so it can only see what that module can see. A normal @SpringBootTest boots the whole application context with every bean, so it can and does depend on everything.
How do I add Spring Modulith to a project?
Import the spring-modulith-bom in your dependencyManagement and add the spring-modulith-starter-test dependency with test scope, which pulls in the verification APIs. Then arrange your business modules as direct sub-packages of the application root package and run the verification and documentation code in a test.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/spring-projects-spring-modulith)Star history chart for your README
[](https://hysenlabs.com/projects/spring-projects-spring-modulith)Was this useful?