Monorepo with Kotlin: Practical Code Organization

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: Why a Monorepo in Kotlin?

\n

In modern Kotlin development, a dilemma often arises: should you store several related projects (backend, microservices, libraries, Android apps) in separate repositories (polyrepo) or combine them into a single monorepo? A monorepo is a code storage strategy where all components reside in a single version control system. For Kotlin projects, this is especially relevant thanks to the powerful Gradle build system and built-in multi-module support.

\n

In this article, we will explore the practical aspects of organizing a monorepo with Kotlin: from directory structure to CI/CD configuration and dependency management. You will learn how to avoid common mistakes and get the maximum benefit from a single code repository.

\n\n

1. Monorepo Structure: From Theory to Practice

\n

Proper structure is the foundation of a successful monorepo. Let's consider a typical project that includes a REST API on Ktor, a database library, and an Android app. Here is the recommended hierarchy:

\n
my-monorepo/\n├── build.gradle.kts          # Root build file\n├── settings.gradle.kts       # Module configuration\n├── gradle.properties         # Common properties\n├── gradle/\n│   └── libs.versions.toml    # Version Catalog\n├── libs/                     # Shared libraries\n│   ├── common-utils/         # Utilities, extensions\n│   ├── common-db/            # Database work (Exposed)\n│   └── common-dto/           # Data Transfer Objects\n├── services/                 # Server applications\n│   ├── user-service/         # User microservice\n│   └── order-service/        # Order microservice\n└── apps/                     # Client applications\n    └── android-app/          # Android app\n
\n

The key element is settings.gradle.kts. It includes all modules:

\n
rootProject.name = "my-monorepo"\n\ninclude("libs:common-utils")\ninclude("libs:common-db")\ninclude("libs:common-dto")\ninclude("services:user-service")\ninclude("services:order-service")\ninclude("apps:android-app")\n
\n

This structure makes it easy to reuse code between services and the client, as well as centrally manage dependency versions.

\n\n

1.1. Version Catalog: Dependency Management

\n

In a monorepo, it is critical to avoid duplicating library versions. Use the Version Catalog (file gradle/libs.versions.toml):

\n
[versions]\nkotlin = "2.0.21"\nktor = "3.0.3"\nexposed = "0.57.0"\n\n[libraries]\nktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }\nexposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }\n\n[plugins]\nkotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }\n
\n

Now in any module you can write:

\n
dependencies {\n    implementation(libs.ktor.server.core)\n    implementation(libs.exposed.core)\n}\n
\n

This ensures that all modules use the same versions and simplifies mass updates.

\n\n

2. Practical Module Organization: Gradle Multi-Project

\n

Each module in a monorepo is a separate Gradle subproject with its own build.gradle.kts. Let's look at the configuration of the common-utils library:

\n
// libs/common-utils/build.gradle.kts\nplugins {\n    alias(libs.plugins.kotlin.jvm)\n}\n\ngroup = "com.mycompany.common"\nversion = "1.0.0"\n\ndependencies {\n    implementation(kotlin("stdlib"))\n    implementation(libs.kotlinx.coroutines)\n}\n
\n

And here is a typical microservice on Ktor:

\n
// services/user-service/build.gradle.kts\nplugins {\n    alias(libs.plugins.kotlin.jvm)\n    alias(libs.plugins.ktor)\n}\n\ndependencies {\n    implementation(project(":libs:common-utils"))\n    implementation(project(":libs:common-dto"))\n    impl                

Blogs

Book Recommendations