Spring Modulith in Practice: From Code to Living Architecture Diagrams

Your architecture diagram in Confluence is six months stale. The one on the wiki still shows the Auth module that was merged into User back in January. The whiteboard in the meeting room? Someone erased it to draw a sprint retrospective.
Architecture documentation has a fundamental problem: it’s maintained by humans, and humans forget. The code changes daily. The diagrams change when someone remembers. Eventually, the gap becomes so large that everyone stops trusting the diagrams and starts reading the code directly — which defeats the purpose of having diagrams at all.
Spring Modulith solves this by generating architecture documentation directly from your code. The diagrams are always accurate because they’re derived from the same source of truth as your application: the module structure itself.
In the previous article, we covered how Modulith enforces module boundaries. This article is about making those boundaries visible — automatically, on every build.
What the Documenter Generates
Spring Modulith ships with a Documenter class that produces three types of output from your ApplicationModules:
1. C4 Component Diagrams
A PlantUML diagram showing all modules and their dependencies — a C4 Level 3 (Component) view of your application:
class DocumentationTests {
ApplicationModules modules = ApplicationModules.of(Application.class);
@Test
void generateArchitectureDocs() {
new Documenter(modules)
.writeModulesAsPlantUml()
.writeIndividualModulesAsPlantUml();
}
}writeModulesAsPlantUml() generates an overview diagram of all modules and their relationships. writeIndividualModulesAsPlantUml() generates a focused diagram for each module, showing only its direct dependencies — useful when the full diagram gets noisy.
The output goes to spring-modulith-docs/ in your build directory by default. Each file is a .puml PlantUML source that renders to a C4-style component diagram.
You can switch between C4 and UML styles:
var options = DiagramOptions.defaults()
.withStyle(DiagramStyle.UML);
new Documenter(modules)
.writeModulesAsPlantUml(options);2. Application Module Canvases
new Documenter(modules)
.writeModuleCanvases();This generates an AsciiDoc table for each module containing:
| Section | What it shows |
|---|---|
| Base package | Module’s root package |
| Spring components | Services, repositories, event listeners — stereotyped by annotation |
| Aggregate roots | Entities with repositories or @Aggregate annotation |
| Published events | Event types the module emits |
| Events listened to | @EventListener / @TransactionalEventListener methods |
| Configuration properties | Spring Boot properties owned by this module |
This is the documentation that’s hardest to maintain manually. Which events does the Venue module publish? What configuration properties does the Notification module expose? The canvas answers these questions from the actual code, not from someone’s memory.
3. Aggregating Document
new Documenter(modules)
.writeAggregatingDocument();Generates a single all-docs.adoc that links all diagrams and canvases into one navigable document. This is what you publish.
All at Once
In practice, you generate everything in a single test:
@Test
void generateFullDocumentation() {
var modules = ApplicationModules.of(Application.class);
new Documenter(modules)
.writeModulesAsPlantUml()
.writeIndividualModulesAsPlantUml()
.writeModuleCanvases()
.writeAggregatingDocument();
}Five lines. That’s your entire architecture documentation pipeline.
Rendering in CI
The Documenter outputs PlantUML source files. To turn them into images, you need a rendering step. Here’s a GitHub Actions workflow that generates docs, renders diagrams, and deploys to GitHub Pages:
# .github/workflows/architecture-docs.yml
name: Architecture Documentation
on:
push:
branches: [main]
jobs:
generate-docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Generate Modulith documentation
run: ./gradlew test --tests '*DocumentationTests*'
- name: Render PlantUML to SVG
uses: cloudbees/plantuml-github-action@master
with:
args: -tsvg build/spring-modulith-docs/*.puml
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: build/spring-modulith-docsEvery push to main regenerates the architecture documentation. The diagrams on GitHub Pages are always in sync with the code. No one needs to remember to update them.
Where to Publish: The Spectrum
Not every team needs GitHub Pages. Here’s how the options compare:
| Approach | Effort | Audience | Always in sync? | Interactive? |
|---|---|---|---|---|
docs/ in repo | Zero | Devs who read Git | On merge | No |
| GitHub Pages | Low | Devs + stakeholders | CI deploys | No |
| IcePanel | Medium | Whole org | Via API | Yes — drill into C4 levels |
| Structurizr | Medium | Architecture teams | DSL push | Yes — C4 native |
Option 1: Keep It in the Repo
The simplest approach — commit the generated docs to a docs/architecture/ directory. This works if your audience is developers who are already in the repo.
Pros: zero infrastructure, versioned alongside code, diffs show architectural changes. Cons: only accessible to people with repo access, raw PlantUML isn’t rendered on GitHub (you’d need to commit rendered SVGs too).
Option 2: GitHub Pages (Recommended Default)
The sweet spot for most teams. CI generates the docs, renders the diagrams, and deploys to a GitHub Pages site. Anyone with the link can see the current architecture — no Git access required.
This is where most teams should start. It’s free, automated, and good enough for 80% of use cases.
Option 3: IcePanel
IcePanel is a collaborative architecture modeling tool with full C4 support. It adds interactive exploration — stakeholders can drill down from system context to containers to components without learning PlantUML.
IcePanel has a REST API, which means you can push Modulith’s output to IcePanel from CI. This bridges the gap between generated-from-code accuracy and interactive visual exploration.
Consider IcePanel when:
- Non-technical stakeholders need to explore the architecture
- You want current vs. future state comparisons
- Multiple teams contribute to the same system and need a shared visual language
Option 4: Structurizr
Structurizr is the reference C4 tooling by Simon Brown (the C4 model creator). It uses its own DSL to define architecture models, from which it generates all C4 diagram levels.
Structurizr is more opinionated than IcePanel — you model in code (Structurizr DSL), not in a visual editor. This fits teams that want “architecture as code” end-to-end.
The trade-off: Structurizr’s DSL is a separate model from your Spring Modulith modules. You’d need to bridge them — either by generating Structurizr DSL from Modulith output, or by maintaining both. For most teams, GitHub Pages with Modulith’s native PlantUML output is simpler.
The Pipeline
Here’s the full picture:
Solid line: the default pipeline that works for most teams. Dotted lines: optional integrations when you outgrow static pages.
What This Gives You
With the Documenter wired into CI:
- Architecture diagrams are always accurate — they’re generated from the code, not drawn by hand
- Module canvases document what manual docs miss — events, listeners, config properties, aggregate roots
- New team members can explore the architecture before reading a single line of code
- Architectural changes are visible in PRs — if a module gains a new dependency, the diagram changes in the same commit
- No maintenance burden — nobody needs to “update the architecture docs” because there’s nothing to update manually
The best architecture documentation is the kind that doesn’t require discipline to maintain. It just happens.
This is Part 2 of the Spring Modulith in Practice series. Part 1: Enforcing Architecture in a Growing Monolith.
Cover photo by Sven Mieke on Unsplash.


