skip to content

Module Verification & Docs

Verification enforces those declared boundaries in a test using ArchUnit, and the documenter generates module diagrams from the same model. Interviewers value it because an architecture rule that is not tested is a suggestion.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

explore

questions

5

What does ApplicationModules.verify() do in Spring Modulith, and how do you wire it into a test?

level: juniorimportance: must knowfreq 62%

answer

  1. ApplicationModules.of(App.class).verify()
  2. direct sub-package = one module
  3. ArchUnit-backed, no Spring context
  4. fails on cycles + internals + disallowed deps
  5. one JUnit test, runs in CI

basics

~20 s

It scans your application's packages, discovers each top-level package as a module, and checks that modules only depend on ones they are allowed to. You run it from a JUnit test; if a rule is broken the test fails.

solid answer

~40 s

Spring Modulith treats each direct sub-package of your main application package as an 'application module'. ApplicationModules.of(MyApplication.class) builds a model of those modules from the classpath. Calling .verify() on that model asserts the structural rules: modules only reach into other modules through allowed paths, no cyclic dependencies exist, and code doesn't reach into another module's internals (non-API packages). It is backed by ArchUnit, so violations are reported as readable failures. You invoke it from a plain JUnit test — typically one small test class — so the check runs on every build and fails fast in CI. It needs no Spring context; it's pure static analysis of compiled classes, making it fast and deterministic.

code

java · 13 lines
java
import org.springframework.modulith.core.ApplicationModules;
import org.junit.jupiter.api.Test;

class ModularityTests {

    static final ApplicationModules modules =
            ApplicationModules.of(ShopApplication.class);

    @Test
    void verifiesModuleBoundaries() {
        modules.verify(); // throws Violations if any boundary rule is broken
    }
}

go deeper

for a junior

Know the one-liner: ApplicationModules.of(App.class).verify() in a JUnit test enforces module boundaries.

for a middle

Explain what it checks (cycles, internals, allowed deps) and that it's ArchUnit-backed static analysis.

for a senior

Discuss module detection rules, exposed-vs-internal packages, and why running it in CI matters.

for a principal

Position it as the enforcement leg of a modular-monolith strategy and its limits (compile-time only, no reflective refs).

## Background **Spring Modulith** is a Spring project for building *modular monoliths*: one deployable app split into logical **application modules** whose boundaries are enforced at build time. A module is, by convention, a **direct sub-package** of your main application package (the one holding the `@SpringBootApplication` class). Example: if your app root is `com.example.shop`, then `com.example.shop.order` and `com.example.shop.inventory` are two modules. Within a module Modulith distinguishes: - The **API / base package** (the module's root package) — types here are *public* to other modules. - **Internal sub-packages** — anything nested deeper (e.g. `order.internal`) is considered module-private; other modules must not reference those types. ## What `verify()` actually does 1. `ApplicationModules.of(ShopApplication.class)` scans the classpath starting at the app's base package and builds an in-memory model of every module, its exposed types, and the dependencies between them. 2. `.verify()` runs a set of **structural assertions** over that model: - **No cyclic dependencies** between modules (A→B→A fails). - **Allowed-dependencies respected**: if a module declares an allow-list, it may only depend on those modules. - **No access to another module's internals**: referencing a type in another module's non-exposed (internal) package fails. - **Efferent/afferent access only through valid entry points** (respecting named interfaces, see follow-ups). If any rule is broken, `verify()` throws a `Violations` exception whose message lists each offending class/dependency, so it reads like a normal test failure. ## How it's backed Under the hood the checks are implemented with **ArchUnit** (a Java architecture-testing library). You don't write ArchUnit rules yourself — Modulith derives them from the module model. That's why it's static (no running Spring context) and fast. ## Wiring it into a test A single JUnit 5 test is the idiomatic setup: ```java class ModularityTests { ApplicationModules modules = ApplicationModules.of(ShopApplication.class); @Test void verifiesModularStructure() { modules.verify(); } } ``` Because it's a normal unit test, it runs in `./gradlew test` / `mvn test` and in CI, blocking merges that break boundaries. ## When to use it Always, once you adopt Modulith — it's the enforcement mechanism that makes module boundaries real rather than aspirational. Keep exactly one such test near the application class. ## Gotchas - It only sees **compiled classes on the classpath**; reflection-only or string-based references are invisible. - Splitting a module into deeper packages doesn't create new modules — only *direct* sub-packages of the base package are modules (unless you customize detection). - It verifies structure, **not** runtime behavior; a passing verify() says nothing about whether your events actually fire.

  • Does verify() need a running Spring application context?
    No. It's pure static analysis over compiled classes via ArchUnit, so it runs like a fast unit test with no context bootstrap.
  • What counts as an application module by default?
    Each direct sub-package of the main application (base) package. Deeper nested packages are treated as that module's internals, not separate modules.

saying these in an interview costs you the question

  • Thinking verify() spins up the Spring context or checks runtime behavior
  • Believing every nested package becomes its own module
  • Claiming you must hand-write ArchUnit rules yourself

context

open as a page

How do you declare which modules a given module is allowed to depend on, and how does verify() catch a violation?

level: middleimportance: must knowfreq 55%

basics

~10 s

You put an @ApplicationModule annotation with allowedDependencies on the module's package-info.java. verify() then fails if any class in that module references a module not on the list.

open as a page

How do you generate module documentation and diagrams with Spring Modulith's Documenter, and what output do you get?

level: middleimportance: should knowfreq 40%

basics

~10 s

You create a Documenter from your ApplicationModules and call methods like writeDocumentation(). It emits PlantUML component diagrams (per module and an overview) plus a Markdown 'module canvas', into target/spring-modulith-docs by default.

open as a page

How do named interfaces change what verify() allows, and why would you use them instead of relying on the default module API?

level: seniorimportance: should knowfreq 33%

basics

~20 s

By default a module exposes every type in its base package. A named interface lets you publish a curated subset (a labeled package or types) so other modules can only use that slice — verify() then fails any access outside it.

open as a page

As an architect, how would you operationalize Modulith verification in CI, and what are its structural limits you must account for?

level: principalimportance: should knowfreq 24%

basics

~20 s

Put verify() in a fast unit test that runs on every build so boundary breaks fail the pipeline, and regenerate Documenter diagrams alongside it. Remember it's compile-time static analysis: reflection, config strings, and runtime wiring are invisible to it.

open as a page