Monorepo with Kotlin: Code Organization in Practice
In modern software development, the choice between a monorepo and a multirepo is increasingly common. For Kotlin projects, especially those using Kotlin Multiplatform or a microservice architecture, a monorepo becomes a powerful tool. In this article, we will break down how to properly organize code in a Kotlin monorepo, look at practical examples and best practices that will help you avoid typical mistakes.
What is a monorepo and why is it needed in Kotlin?
A monorepo is an approach where all the code of a project (or several related projects) is stored in a single repository. In the context of Kotlin, this can include:
- Shared libraries — utilities, data models, DTOs.
- Application modules — backend (Ktor, Spring), frontend (Kotlin/JS), mobile applications (Kotlin Multiplatform).
- Scripts and configurations — CI/CD, Dockerfile, Gradle scripts.
The main advantages of a monorepo for Kotlin projects:
- Single version of dependencies — all modules use the same version of Kotlin and libraries, reducing the risk of conflicts.
- Simplified refactoring — changing an API in one module is immediately visible in all dependent modules.
- Easier CI/CD setup — one pipeline for all components.
- Transparency — the entire codebase is available in one place.
However, there are also disadvantages: the repository size can grow, and cloning time increases. But for medium and large Kotlin projects, a monorepo is often the optimal solution.
Monorepo structure: a practical template
Proper folder organization is the foundation of success. Let's look at a typical structure for a Kotlin project using Gradle:
my-kotlin-monorepo/├── build.gradle.kts # Root build script├── settings.gradle.kts # Module configuration├── gradle.properties # Common Gradle parameters├── gradle/│ └── libs.versions.toml # Version Catalog (recommended)├── modules/│ ├── common/ # Shared utilities and models│ │ ├── build.gradle.kts│ │ └── src/│ ├── backend/ # Backend service on Ktor│ │ ├── build.gradle.kts│ │ └── src/│ ├── android-app/ # Android application│ │ ├── build.gradle.kts│ │ └── src/│ └── web-client/ # Kotlin/JS client│ ├── build.gradle.kts│ └── src/├── scripts/ # Helper scripts│ └── deploy.sh└── docs/ # Documentation └── architecture.mdThe settings.gradle.kts file includes the modules:
rootProject.name = "my-kotlin-monorepo"
include(":modules:common")include(":modules:backend")include(":modules:android-app")include(":modules:web-client")In the root build.gradle.kts, you can define common plugins and dependencies:
plugins { kotlin("jvm") version "1.9.22" apply false kotlin("multiplatform") version "1.9.22" apply false id("com.android.application") version "8.2.0" apply false}Dependency management: Version Catalog and Convention Plugins
In a monorepo, it is critically important to avoid duplicating dependency versions. For this, Kotlin uses the Version Catalog (the libs.versions.toml file).
Example of gradle/libs.versions.toml:
[versions]kotlin = "1.9.22"ktor = "2.3.7"coroutines = "1.8.0"
[libraries]kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }ktor-server-core = { module = "io.k