Introduction: what is a monorepo and why is it needed in Java
In the world of Java development, there are two main approaches to storing code: monorepo and multirepo. A monorepo is a strategy where all project codebases (libraries, services, utilities, configurations) are stored in a single repository. For Java developers working with large enterprise systems or microservices, the monorepo is becoming an increasingly popular solution.
The advantages of a monorepo in Java are obvious:
- Single version of dependencies: All modules use the same version of libraries, which eliminates conflicts (classpath hell).
- Simplified refactoring: Changing an API in one module is immediately visible in all dependent modules.
- Cross-cutting change history: One commit can contain changes in several modules, which simplifies debugging.
- Standardization: Unified rules for code style, linting, and CI/CD for the entire project.
However, a monorepo requires proper organization. In this article, we will look at how to properly structure a Java project in a monorepo using Maven and Gradle.
Main part: organizing a monorepo in Java
1. Directory structure: modular approach
The foundation of a monorepo is a modular structure. Each module represents an independent unit (library, service, utility). Let's consider a typical structure for a Java project:
my-monorepo/├── build.gradle # Root build file (Gradle)├── settings.gradle # List of modules├── gradle.properties # Common settings├── gradle/│ └── wrapper/├── common/ # Common libraries│ ├── common-utils/ # Utilities (strings, dates)│ └── common-dto/ # DTOs for data exchange├── services/ # Microservices│ ├── user-service/ # User service│ └── order-service/ # Order service├── libs/ # Internal libraries│ └── logging-lib/ # Logging library├── tests/ # Integration/end-to-end tests│ └── e2e-tests/└── buildSrc/ # Common plugins and configurationsEach module inside has a standard Maven/Gradle structure:
user-service/├── build.gradle├── src/│ ├── main/│ │ ├── java/│ │ │ └── com/example/user/│ │ │ ├── controller/│ │ │ ├── service/│ │ │ └── repository/│ │ └── resources/│ │ └── application.yml│ └── test/│ └── java/└── Dockerfile2. Dependency management: Maven BOM and Gradle Version Catalog
The main problem of a monorepo is synchronizing dependency versions. In Java, this is solved in two ways.
Option 1: Maven BOM (Bill of Materials)
A root POM is created that declares all dependency versions. Child modules use dependencyManagement:
<!-- pom.xml (root) --><dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>3.2.0</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.example</groupId> <artifactId>common-utils</artifactId> <version>1.0.0</version> </dependency> </dependencies></dependencyManagement>Option 2: Gradle Version Catalog
In the file gradle/libs.versi