Deep Dive Spring Modulith Part 1: Functional Modules in Focus
Spring Modulith supports the development of functionally modularized Spring Boot applications.
(Image: LilKar/Shutterstock.com)
- Nils Hartmann
With Spring Modulith, it is possible to divide Spring Boot applications into functional modules with clear interfaces, which communicate via events and can be tested in isolation. The architecture of the modules can be automatically monitored and documented. This series of articles introduces Spring Modulith, whose version 2.0 was released in November 2025, in two parts. The first part covers the basics of module and event-based architecture. Part 2 will look at module testing capabilities, among other things.
From Classic Architectures to the Modular Monolith
Spring Boot is a typical choice for developing Java-based applications. To keep the code developed with it manageable in the long term, architectural patterns such as layered, hexagonal, or onion architecture are often applied. However, these patterns often only divide the application into rather technical components, for example, to enable decoupling from databases or other external systems. In the classic layer architecture, for instance, controllers access services, and services access repositories, but not vice versa.
In the hexagonal architecture, individual components such as the UI and persistence layers, as well as the core business logic, are separated from each other via "ports" and "adapters." This is intended to separate domain logic from technical details, such as the connection to a specific database. Furthermore, these architectural styles are intended to enable individual parts of the application to be tested in isolation and to be interchangeable at any time, for example, when switching databases. Spring Modulith takes a different approach.
Modulith is a portmanteau of "modular monolith." The application is developed as a monolith but is internally divided into strictly functional modules (also called slices). The potentially complex core logic of an application is thus to be divided into manageable smaller units that are self-contained, have clear interfaces, and contain everything necessary to implement the respective functionality. In the best case, changes to functional requirements thus only affect exactly one module. The modules of an application can be implemented according to the architectural styles mentioned above, but they don't have to be. Similar to microservices, the most suitable architecture can be chosen per module – at least within a certain framework.
This article introduces Spring Modulith using the example application "Plantify." Plantify allows customers to have their plants cared for. To do this, they first register their plants. Plantify creates care tasks for each plant, which are then processed by a service provider. Plantify regularly sends an invoice to the customers for the tasks performed. Communication with the application takes place via an HTTP API. The source code of the example application can be found on GitHub.
(Image:Â buraratn/123rf)
At the online conference betterCode() Spring, the morning will focus on secure applications with Spring Security and the integration of AI with Spring AI. The afternoon is dedicated to Spring Boot, showcasing the innovations in version 4 in conjunction with Java 25, tips for container integration, and practical Spring Boot hacks.
Application Modules with Spring Modulith
To use Spring Modulith in your own application, add its starter package to your Maven or Gradle configuration. That's all it takes for Spring Modulith to classify and treat packages in the application differently. Spring Modulith considers all packages directly under the root package (the package with the SpringBootApplication class) as "Application Modules." Technically, they remain normal Java packages, but Spring Modulith imposes some rules on the use of these Application Modules. For example, there must be no direct or indirect circular dependencies between Application Modules.
In the Plantify application, for example, there are three packages directly under the root package nh.plantify: plant, care, and billing. Spring Modulith interprets these three packages as Application Modules. In the application, the plant Application Module accesses care (e.g., to create care tasks for a new plant). Furthermore, care accesses billing to calculate the setup fee. These dependencies are allowed because they only point in one direction. However, if the application were extended so that a class from the billing module accessed the plant module, this would result in a circular dependency. Spring Modulith detects and prohibits this because this type of dependency often leads to problems in further development.
Videos by heise
Compliance with the rules can be checked and ensured in several ways with Spring Modulith. On the one hand, Spring Modulith can check them every time the application starts and report errors if rules are violated. However, this takes time and is only done late in the development process. Alternatively, a JUnit test can be implemented to check the rules. A corresponding example can be found in Listing 1.
class PlantifyModuleTest {
static ApplicationModules modules = ApplicationModules.of(PlantifyApplication.class);
@Test
void verifyModules() {
modules.verify();
}
@Test
void writeDocumentationSnippets() {
new Documenter(modules)
.writeModulesAsPlantUml();
}
}
Listing 1: The module structure is checked and visualized in the JUnit test
The ApplicationModules class represents the detected Application Modules in the given package. The verify method ensures within the test that all rules are adhered to. If violations occur, the test fails with a detailed error message (see Figure 1).
These tests, along with all other "normal" tests of an application, are run continuously, so problems are detected quickly. The ApplicationModules class can also output the module structure as a C4 diagram, which also shows the type of usage of a module (more on this later). The visualization of the Plantify modules at this point can be seen in Figure 2.
Internal Modules
In addition to prohibiting circular dependencies, Spring Modulith applies another rule by default: all subpackages within an Application Module are considered "internal." Classes from these packages can be used freely within the Application Module, but access from other modules is prohibited. In other words, the root package of an Application Module is its public API. Only the public classes are available to other modules. The test case shown above also checks compliance with this rule. Additionally, IDEs such as IntelliJ, Eclipse, or VS Code with the appropriate plug-ins can check these rules and display them directly in the source code. Figure 3 shows a corresponding example in Eclipse with the Spring Tools.
Here, the CareTaskService, located in the care module, accesses the InvoiceGenerator class, which is in the invoice subpackage of the billing module. Without Spring Modulith, access would be compliant from a Java perspective, as the InvoiceGenerator class is public and thus can be used by all packages.