Multimodule Spring Boot Projects with Maven/Gradle: Best Practices
Structuring large-scale enterprise applications often demands modularity, separation of concerns, and efficient dependency management. A multimodule Spring Boot project enables you to break down a complex system into manageable, isolated modules—each with its own purpose but capable of interacting seamlessly. In this article, we’ll explore the best practices for organizing multimodule Spring Boot projects using Maven and Gradle.
1. Why Go Multimodule?
Multimodule architecture brings the following advantages:
- Separation of concerns: Keep domain logic, web interfaces, persistence, and shared utilities in distinct modules.
- Improved build performance: Only affected modules are rebuilt.
- Better testing and deployment: Modules can be tested and deployed independently.
- Clear dependency management: Enforce directional dependencies.
2. Typical Module Structure
A common multimodule layout looks like this:
root-project/ │ ├── api/ # DTOs and interfaces (no Spring dependencies) ├── core/ # Business logic and domain models ├── persistence/ # Database access (e.g. JPA, Repositories) ├── web/ # REST controllers, Spring Boot main class └── shared/ # Common utilities, enums, and exceptions
Module Dependencies
webdepends oncoreandapicoredepends onapi,shared, andpersistencepersistencedepends onsharedapiandsharedare dependency roots and should not depend on any other module
This promotes clean layering and proper inversion of control:
- Define interfaces and DTOs in
api - Implement those interfaces in
core - Reference only the interfaces from
weborcore, ensuring directionality is preserved
Here is a visual representation of the dependency flow:
3. Maven Setup
In your root pom.xml:
<modules>
<module>api</module>
<module>core</module>
<module>persistence</module>
<module>web</module>
<module>shared</module>
</modules>
Each module has its own pom.xml with dependencies only on needed modules.
Use the dependencyManagement block in the root pom.xml to control versions centrally..
4. Gradle Setup
In your root settings.gradle.kts:
include("api", "core", "persistence", "web", "shared")
Each build.gradle.kts applies plugins and declares dependencies:
dependencies {
implementation(project(":core"))
implementation(project(":shared"))
}
Use version catalogs or a shared dependencies.gradle.kts file to manage versions centrally.
5. Dependency Isolation
Avoid circular dependencies and maintain boundaries using interface-driven design:
- Define contracts (interfaces, DTOs) in
api - Implement logic in
core - Reference only
apiinweborcore - Keep
apiandsharedfree from Spring or implementation-specific code
Use Spring’s @ComponentScan(basePackages = ...) to limit scanning scope per module.e.
6. Running the Application
Place the @SpringBootApplication main class in the web module. Ensure all dependent modules are listed as dependencies in its pom.xml or build.gradle.kts.
To run:
./mvnw spring-boot:run -pl web
or
./gradlew :web:bootRun
7. Best Practices
- Avoid transitive dependencies: Declare all needed dependencies explicitly.
- Use
apiandsharedfor contracts and utilities: Keep them implementation-free. - Enforce boundaries with rules: Use tools like ArchUnit to verify dependency direction.
- Test in isolation: Unit test each module independently; only the
webmodule should contain full-stack integration tests. - Document module APIs and dependencies clearly for future maintainers.
8. Conclusion
Modularizing your Spring Boot application fosters a maintainable, testable, and scalable architecture. Whether you choose Maven or Gradle, follow these best practices to keep your codebase clean and your team productive. With clear module separation and thoughtful dependency design, multimodule Spring Boot projects can scale effectively with your business needs.






Why does the api have a dependency on the core? If it uses interfaces defined in api, isn’t the relationship already covered by IoC?
Hello Chris, Great point! You’re absolutely right—api should not depend on core. That would break the intended direction of dependencies and violate inversion of control. Initially, I modeled the dependencies based on functionality flow rather than strict architectural direction. That led to the incorrect assumption that api might rely on core. After reviewing the layering principles and applying inversion of control more strictly, i’ve corrected this in the updated version: core now depends on api, not the other way around. This ensures that api remains a pure contract layer, free from business logic or Spring dependencies, and keeps the architecture… Read more »
The providing module structure in simply wrong from DDD point of view