Monorepository with Java: Code Organization and Best Practices

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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 configurations


Each 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/└── Dockerfile


2. 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

Blogs

Book Recommendations