The Java compiler accepts a public internal class, but ApplicationModules.verify can reject cross-module access to it.
Spring Modulith verification: a compiling internal import still fails architecture checks
Test both sides
The source kit runs two architecture checks. Its valid dispatch module references the inventory root API and passes. Its invalid dispatch module constructs inventory.internal.UnsafeStockStore; verification throws despite both fixtures compiling. These tests demonstrate the package rule. They do not start the Spring context or execute a listener. The API lesson shows the accepted dependency.
Run on the real root
The accepted and rejected package trees are separate roots so one intentional violation does not poison the valid fixture. In a product, verify the actual application root in CI. Do not disable the rule globally because a public internal type seems convenient; move required behavior into an explicit root API or named interface.
Checked excerpt
assertDoesNotThrow(() -> ApplicationModules.of(
ReceiptModulesApplication.class).verify());
assertThrows(RuntimeException.class, () -> ApplicationModules.of(
InvalidReceiptModulesApplication.class).verify());Cost and verification
Architecture scans consume build time, not request time. They catch coupling before broad integration tests become difficult to isolate. The isolated source-kit project checks the stated local behavior.
Common Mistakes
- Do not assume public means intended as a module API.
- Do not verify only a tiny fixture when the production graph is the subject.
- Do not claim these checks cover event delivery.
Read next
Spring Modulith module APIs: call the inventory root package, not its internals, Spring Modulith module tests: distinguish structure verification from a sliced context, Spring tests: separate business rules, wiring and transport, Spring Core.
