skip to content

Application Modules

A module is a top-level package with a published API and declared allowed dependencies, expressed in code rather than in a wiki. Interviewers ask how you stop a monolith turning into a ball of mud, and this is a concrete answer.

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

explore

questions

5

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

open as a page

How does Spring Modulith decide which types of a module are public API versus internal, and how is that enforced?

level: middleimportance: must knowfreq 65%

basics

~10 s

Types directly in the module's base package are public API; types in nested sub-packages are internal. Other modules referencing internal types cause ApplicationModules.of(App.class).verify() to fail.

open as a page

How do you declare and constrain a module's allowed dependencies with @ApplicationModule, and what happens if a module talks to one not listed?

level: seniorimportance: must knowfreq 60%

basics

~10 s

Put @ApplicationModule(allowedDependencies = {"inventory"}) in the module's package-info.java. The module may then only depend on listed modules; verify() fails if it references any other module.

open as a page

What problem do named interfaces (@NamedInterface) solve in Spring Modulith, and how do they interact with allowedDependencies?

level: seniorimportance: should knowfreq 40%

basics

~20 s

By default only a module's base-package types are exposed. A named interface (@NamedInterface) publishes a specific sub-package as an additional named entry point, so other modules can depend on just that slice via 'module :: interfaceName'.

open as a page

You're introducing Spring Modulith into an existing Spring Boot monolith. How do you model modules, verify boundaries in CI, and roll it out without breaking the build on day one?

level: principalimportance: should knowfreq 30%

basics

~10 s

Reorganize code so each business area is a direct sub-package with internals under nested packages, add one test calling ApplicationModules.of(App.class).verify() in CI, and adopt incrementally — start permissive, then add allowedDependencies module by module.

open as a page