skip to content

In Spring Modulith, what defines an 'application module' and how do you obtain the module model in code?

level: juniorimportance: must knowfreq 70%

answer

  1. Direct sub-package of main app package = one module
  2. Top-level types = API; .internal = hidden
  3. ApplicationModules.of(App.class)
  4. Test/build-time static model, not runtime
  5. Iterable<ApplicationModule>, .verify(), Documenter

basics

~10 s

By default an application module is a direct sub-package of your main application class's package. You get the model in a test with ApplicationModules.of(Application.class).

solid answer

~30 s

Spring Modulith treats each direct sub-package of the package containing your `@SpringBootApplication` class as one application module. So if your app lives in `com.example.app`, then `com.example.app.order` and `com.example.app.inventory` are two modules. The module's top-level types are its public API; types in nested sub-packages (e.g. `order.internal`) are considered internal and hidden from other modules. You build the module model programmatically with `ApplicationModules.of(Application.class)`, which scans the class's package and returns an `ApplicationModules` instance you can iterate, verify (`.verify()`), or render into documentation. This is a compile/test-time model derived from package structure and bytecode — there's no runtime container change.

code

java · 18 lines
java
// com.example.app.Application (main class)
@SpringBootApplication
class Application {}

// A test that builds the module model from the package structure
class ModularityTests {

    @Test
    void printsModules() {
        ApplicationModules modules = ApplicationModules.of(Application.class);
        modules.forEach(System.out::println); // one line per module
    }
}

// Packages:
// com.example.app.order        -> module "order" (public: OrderService)
// com.example.app.order.internal -> internal to "order" (hidden)
// com.example.app.inventory    -> module "inventory"

go deeper

for a junior

Know: module = direct sub-package of the main app package, and ApplicationModules.of(App.class) builds the model.

for a middle

Add the API-vs-internal rule: top-level types are exposed, nested sub-packages are hidden.

for a senior

Explain it's static test-time analysis (ArchUnit-based), no runtime change, plus Documenter/verify usage.

for a principal

Discuss simple vs advanced arrangement, exclusion predicates, and positioning this as a modular-monolith enforcement strategy vs microservices.

**Spring Modulith** is a Spring project that helps you structure a Spring Boot monolith into well-defined logical modules, then *verify* those boundaries automatically instead of relying on discipline alone. **What is an application module?** With the default (simple) arrangement, an application module is **one direct sub-package** of the package that contains your main `@SpringBootApplication` class (the 'application main package'). Example: with main class in `com.example.app`, the packages `com.example.app.order`, `com.example.app.inventory`, and `com.example.app.catalog` are each a distinct application module. The module *is* the package (plus its sub-packages). **API vs internals.** Types that sit **directly** in the module's base package (e.g. `com.example.app.order.OrderService`) form the module's **public API** — other modules may depend on them. Types in **nested sub-packages** (e.g. `com.example.app.order.internal.OrderRepository`) are treated as **module-internal** and are *not* allowed to be referenced from other modules. This is the core rule Modulith enforces: cross-module access is only legal against another module's exposed (top-level) types. **Getting the model.** You create the in-memory module model with the static factory: ```java ApplicationModules modules = ApplicationModules.of(Application.class); ``` This uses the class's package as the scanning root. The resulting `ApplicationModules` object is `Iterable<ApplicationModule>`; you can print it (`modules.forEach(System.out::println)`), verify boundaries (`modules.verify()`), or feed it to the `Documenter` to generate C4/PlantUML diagrams and module canvases. **Where it runs.** This model is built at **test/build time** by analyzing packages and bytecode (Modulith builds on **jQAssistant/ArchUnit-style** static analysis). It does *not* alter the runtime Spring `ApplicationContext` — your app still boots as a normal Spring Boot monolith. The value is the *verification*, not any runtime isolation. **Two arrangement styles.** The above is the **simple** arrangement (module = single package). Modulith also supports an **advanced** arrangement where a module base package may contain further nested application-level packages; there you use **named interfaces** and explicit configuration to expose more than just the base package. Most codebases start simple. **Excluding packages.** `ApplicationModules.of(Application.class, JavaClass.Predicates...)` accepts an exclusion predicate to drop packages (e.g. generated code) from the model. **When to use.** Reach for application modules when a Spring Boot monolith is growing and you want enforced internal boundaries (a 'modular monolith') without splitting into microservices — you get compile-safe encapsulation plus generated documentation.

  • If OrderService is in com.example.app.order.internal instead of com.example.app.order, can InventoryService use it?
    No. Anything under an `internal` (nested) sub-package is module-private, so a reference from the inventory module would be flagged as a boundary violation by `verify()`.
  • Does building the ApplicationModules model change how the app runs at runtime?
    No. It's a static, test/build-time analysis of packages and bytecode. The app still boots as an ordinary Spring Boot monolith; Modulith only verifies and documents structure.

saying these in an interview costs you the question

  • Thinking modules change runtime wiring or create separate contexts
  • Believing every package (including nested internal ones) is its own module
  • Assuming you need an annotation on every module for it to be recognized

context