Most Modular Monoliths I have worked on started with boundaries everyone agreed on in a design session. A few months later someone needs a customer's credit limit inside Payments, the internal class that holds it is one import away, and the pull request goes through because nothing in the build objects. Nothing physically stops a module from reaching into another module's internals, so the boundary holds only as long as every developer on every change decides it should.
Architecture tests move that decision into the build. On JVM codebases I have used three ways of doing it: writing the rules directly in ArchUnit, adopting Spring Modulith's verification, and Modulith Rules, the rule set I maintain. They overlap a great deal, and the choice between them depends less on features than on how much of the module model you want to write down yourself and how closely you are willing to tie it to Spring.
Plain ArchUnit
ArchUnit gives you an assertion language over compiled classes and leaves the module model to you. For one or two rules that is the simplest thing that works. A test saying nothing outside ..payments.. may depend on ..payments.internal.. is a few lines, and every developer can read it. The cost shows up as the module count grows. Each rule carries its own idea of where a module starts, and when someone renames a package the rules drift apart without anyone noticing. ArchUnit's freezing support is worth knowing whichever way you go, because it records the violations a codebase already has and fails only on new ones, which is usually the only way these tests get into an existing system at all.
Spring Modulith
Spring Modulith derives modules from the package structure of a Spring Boot application and verifies the dependencies between them. Much of its value is at runtime: an event publication registry so events between modules survive a restart, module-scoped integration tests, per-module observability, and documentation generated from the same model. If the application is Spring throughout and you want that runtime support, it is the more complete option, and the Spring team stands behind it. The trade-off is that the module model follows Spring's conventions, so a codebase that departs from them, or does not use Spring, gets less out of it.
Modulith Rules
Modulith Rules sits closer to plain ArchUnit. It is a set of ArchUnit rules that all read from one module declaration, written in code or YAML, so every rule agrees on where a module starts. It adds checks on the direction of communication between two modules, for example that Ordering may reach Notifications only through events, and each failure message ends with a suggested change, because in my experience a message that only names the violation is the one that gets suppressed. The core has no Spring dependency, and an optional module adds checks such as @Transactional methods that reach across modules.
The limits are real. It runs only at test time, so it knows nothing about runtime events, shared database tables or reflection, and that is where a lot of real coupling ends up. It is also a young project at 0.3.0 with far fewer users than either alternative, which is fair to weigh against a framework with a team behind it.
How I would choose
If all you need is two or three rules, I would write them straight in ArchUnit and not take on another dependency. Once the application is Spring Boot and the runtime features start to matter, Spring Modulith earns its place. Modulith Rules fits between the two, for a codebase with enough modules that keeping a dozen hand-written rules in agreement has become a maintenance job of its own, or for a team that most wants the direction of communication checked. Running Spring Modulith for the runtime and Modulith Rules for the stricter test-time checks also works, since Modulith Rules only adds tests.
Whichever you pick, what decides whether it lasts is how the failure message reads to a developer who hits it for the first time under a deadline.
There is a worked example on Foojay that takes one rule from failure to fix at https://foojay.io/today/enforce-java-module-boundaries-with-modulith-rules/, and the code is at https://github.com/modular-monolith/modular-monolith-rules.

