Products Consulting About Blog Contact Us Česky
arrow_back Back to blog

Spring Modulith in Practice: From Code to Living Architecture Diagrams

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:

Java
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:

Java
var options = DiagramOptions.defaults()
    .withStyle(DiagramStyle.UML);

new Documenter(modules)
    .writeModulesAsPlantUml(options);

2. Application Module Canvases

Java
new Documenter(modules)
    .writeModuleCanvases();

This generates an AsciiDoc table for each module containing:

SectionWhat it shows
Base packageModule’s root package
Spring componentsServices, repositories, event listeners — stereotyped by annotation
Aggregate rootsEntities with repositories or @Aggregate annotation
Published eventsEvent types the module emits
Events listened to@EventListener / @TransactionalEventListener methods
Configuration propertiesSpring 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

Java
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:

Java
@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:

YAML
# .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-docs

Every 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:

ApproachEffortAudienceAlways in sync?Interactive?
docs/ in repoZeroDevs who read GitOn mergeNo
GitHub PagesLowDevs + stakeholdersCI deploysNo
IcePanelMediumWhole orgVia APIYes — drill into C4 levels
StructurizrMediumArchitecture teamsDSL pushYes — 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).

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:

graph LR A[Code change] -->|push to main| B[CI runs tests] B -->|DocumentationTests| C[Modulith Documenter] C -->|PlantUML + AsciiDoc| D[Render to SVG] D -->|Deploy| E[GitHub Pages] D -.->|API push| F[IcePanel] D -.->|DSL bridge| G[Structurizr]

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.

More from the Blog