Monorepository with Java: Organizing Code in Practice
\nIn modern Java development, the question of choosing between a monorepository (monorepo) and multiple separate repositories is increasingly common. A monorepository is an approach where all project code (multiple services, libraries, configurations) is stored in a single Git repository. In this article, we will explore how to organize a monorepository with Java in practice: from folder structure to build configuration and CI/CD. You will get ready-made code examples and recommendations to help avoid common mistakes.
\n\nWhy a Monorepository for Java?
\nA monorepository is especially useful for Java projects where many modules depend on each other. The main advantages are:
\n- \n
- Single version of dependencies — all modules use the same version of libraries, eliminating conflicts. \n
- Simplified refactoring — changing an API in one module is immediately visible in all dependent modules. \n
- Shared build configuration — Gradle or Maven is configured once for all modules. \n
- CI/CD integrity — one pipeline for all services, making it easier to track changes. \n
However, a monorepository requires discipline: without a clear structure, it turns into chaos. Let's look at proper organization.
\n\nMonorepository Structure for a Java Project
\nRecommended structure for a monorepository with Java (using Gradle as an example):
\nmy-monorepo/\n├── build.gradle.kts # root build file\n├── settings.gradle.kts # includes all modules\n├── gradle.properties # common properties\n├── libs/ # shared libraries\n│ ├── common-utils/\n│ │ ├── build.gradle.kts\n│ │ └── src/\n│ └── logging/\n│ ├── build.gradle.kts\n│ └── src/\n├── services/ # microservices\n│ ├── user-service/\n│ │ ├── build.gradle.kts\n│ │ └── src/\n│ └── payment-service/\n│ ├── build.gradle.kts\n│ └── src/\n├── config/ # configurations (e.g., checkstyle, spotbugs)\n│ └── checkstyle.xml\n└── docker/ # Dockerfile for each service\n ├── user-service.Dockerfile\n └── payment-service.Dockerfile\n\nKey principles:
\n- \n
- Modules are divided into
libs(libraries) andservices(executable applications). \n - The root
build.gradle.ktsdefines common plugins and versions. \n - The
settings.gradle.ktsfile includes all subprojects. \n
Example of settings.gradle.kts:
rootProject.name = "my-monorepo"\n\ninclude(\n "libs:common-utils",\n "libs:logging",\n "services:user-service",\n "services:payment-service"\n)\n\n\nConfiguring Dependencies Between Modules
\nThe main task of a monorepository is to manage internal dependencies. Let's look at an example where user-service depends on common-utils.
Step 1. In the root gradle.properties, declare versions:
# gradle.properties\nkotlinVersion=1.9.0\nspringBootVersion=3.2.0\ncommonUtilsVersion=1.0.0\n\nStep 2. In the root build.gradle.kts, include plugins and set common settings:
plugins {\n id("java-library") apply false\n id("org.springframework.boot") version "3.2.0" apply false\n id("io.spring.dependency-management") version "1.1.4" apply false\n}\n\nallprojects {\n group = "com.example"\n version = "1.0.0"\n\n repositories {\n mavenCentral()\n }\n}\n\nsubprojects {\n apply(plugin = "java-library")\n\n java {\n sourceCompatibility = JavaVersion.VERSION_17\n targetCompatibility = JavaVersion.VERSION_17\n }\n\n dependencies {\n implementation("org.slf4j:slf4j-api:2.0.9")\n testImplementation("org.j