How do you declare and constrain a module's allowed dependencies with @ApplicationModule, and what happens if a module talks to one not listed?
answer
- @ApplicationModule(allowedDependencies=...) in package-info.java
- Default = depend on any module; annotate to restrict
- {} empty = depend on nothing
- 'module :: interface' targets a named interface
- verify() fails on unlisted dep or cycle; no runtime block
basics
~10 sPut @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.
solid answer
~40 sYou declare it in the module's `package-info.java`: `@ApplicationModule(allowedDependencies = { "inventory", "payment" })`. This whitelists exactly which other modules this one may depend on. By default (no annotation) a module may depend on **any** other module's exposed types, so you add `allowedDependencies` to lock a module down. Once declared, `ApplicationModules.of(App.class).verify()` fails the build if the module references any module *not* in the list — even a module that would otherwise be legal. You can target a specific named interface with the `moduleName :: interfaceName` syntax (e.g. `"order :: spi"`). An empty `allowedDependencies = {}` means the module may depend on **no** other module. Shared/open modules and the standard Spring infrastructure are still reachable. This turns intended architecture into an enforced, documented contract.
code
java · 11 lines// com/example/app/order/package-info.java
@ApplicationModule(
allowedDependencies = { "inventory", "payment :: api" }
)
package com.example.app.order;
import org.springframework.modulith.ApplicationModule;
// If OrderService references com.example.app.shipping.*,
// ApplicationModules.of(Application.class).verify() fails:
// "Module 'order' depends on non-allowed module 'shipping' ..."go deeper
Know it exists: an annotation that lists which modules a module may use.
Place it in package-info.java and know verify() fails on unlisted dependencies.
Explain default-permissive vs empty-array semantics, named-interface targeting, and static test-time enforcement.
Discuss encoding target architecture, name-based (non-compile-checked) module references, incremental adoption, and cycle prevention.
**Default dependency policy.** Out of the box, Spring Modulith lets any module reference any **other module's exposed (base-package) types**. Encapsulation of *internals* is always on, but *which* modules you may couple to is unrestricted until you say otherwise. That's usually too loose for a large system. **Declaring allowed dependencies.** You tighten a specific module by annotating its **package** — placed in `package-info.java` at the module's base package: ```java @org.springframework.modulith.ApplicationModule( allowedDependencies = { "inventory", "payment" } ) package com.example.app.order; ``` (In Kotlin the equivalent goes in a `package-info.java` or a `@file:`-annotated module-info-style file; Java uses `package-info.java`.) Now the **order** module is permitted to depend **only** on the `inventory` and `payment` modules. Any reference to a third module (say `shipping`) becomes a **verification failure**. **Semantics of the list:** - **Not present / no annotation:** depend on any module (default permissive). - **`allowedDependencies = {}` (empty):** depend on **no** other module — a fully standalone/leaf module. - **`allowedDependencies = {"inventory"}`:** depend on inventory only. - **Named-interface targeting:** `"order :: spi"` restricts the dependency to a specific **named interface** of that module rather than its whole API, so you can allow coupling to a narrow SPI while forbidding the rest. **Enforcement.** As with all boundary rules, this is checked statically by `ApplicationModules.of(App.class).verify()` (ArchUnit-based) at **test/build time**. If **order** imports/references a type from **shipping** while shipping isn't listed, verify() fails with a message identifying the illegal dependency and the offending types. There is no runtime enforcement — the app still wires beans normally. **What still remains reachable.** Declaring `allowedDependencies` constrains **application-module** coupling. It does not stop you from using framework/library types or modules marked as **open/shared** (via `@ApplicationModule(type = Type.OPEN)` or the shared-module mechanism), which are treated as globally available infrastructure. **Cycles.** `allowedDependencies` also helps prevent cycles: if order allows inventory and inventory allows order, verify() reports a **cyclic dependency** between modules, which is always illegal regardless of the whitelist. **Gotchas & practice.** - The annotation lives on the **package**, not on a class — a common mistake is annotating a random type. - Forgetting to list a *transitively needed* module produces a verify failure even though the code compiles fine. - Because it's opt-in per module, teams often start permissive and progressively add `allowedDependencies` to the most sensitive modules first. - Refactoring: renaming a module (its base package) breaks string references in other modules' `allowedDependencies` — those are name-based, not compile-checked, so a typo silently means 'unknown module' at verify time. **When to use.** Use `allowedDependencies` to codify a target architecture (e.g. a layered or hexagonal dependency direction) so the build fails the moment someone introduces unwanted coupling — the payoff of a modular monolith over convention-only structuring.
- What's the difference between omitting allowedDependencies entirely and setting it to an empty array?Omitting it keeps the permissive default — the module may depend on any other module's exposed types. An empty array `{}` forbids all inter-module dependencies, making it a standalone/leaf module.
- How would you allow order to use only a narrow SPI of payment, not its whole API?Expose that slice as a named interface in payment (e.g. `@NamedInterface("spi")`), then declare `allowedDependencies = { "payment :: spi" }` on the order package.
- Where must the @ApplicationModule annotation be placed?On the module's base package, i.e. in that package's `package-info.java` — not on an individual class.
saying these in an interview costs you the question
- Thinking allowedDependencies is just documentation with no enforcement
- Confusing empty {} (nothing allowed) with omission (everything allowed)
- Placing @ApplicationModule on a class instead of the package
- Assuming the module-name strings are compile-checked (they're name-based)