Monorepository with Java: Organizing Code in Practice

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

Monorepository with Java: Organizing Code in Practice

\n

In 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\n

Why a Monorepository for Java?

\n

A 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
\n

However, a monorepository requires discipline: without a clear structure, it turns into chaos. Let's look at proper organization.

\n\n

Monorepository Structure for a Java Project

\n

Recommended structure for a monorepository with Java (using Gradle as an example):

\n
my-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
\n

Key principles:

\n
    \n
  • Modules are divided into libs (libraries) and services (executable applications).
  • \n
  • The root build.gradle.kts defines common plugins and versions.
  • \n
  • The settings.gradle.kts file includes all subprojects.
  • \n
\n

Example of settings.gradle.kts:

\n
rootProject.name = "my-monorepo"\n\ninclude(\n    "libs:common-utils",\n    "libs:logging",\n    "services:user-service",\n    "services:payment-service"\n)\n
\n\n

Configuring Dependencies Between Modules

\n

The main task of a monorepository is to manage internal dependencies. Let's look at an example where user-service depends on common-utils.

\n

Step 1. In the root gradle.properties, declare versions:

\n
# gradle.properties\nkotlinVersion=1.9.0\nspringBootVersion=3.2.0\ncommonUtilsVersion=1.0.0\n
\n

Step 2. In the root build.gradle.kts, include plugins and set common settings:

\n
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                

Blogs

Book Recommendations