Skip to main content

Kotlin Project Structure

When you're starting with Kotlin development, understanding how to structure your projects properly is crucial for maintainability, scalability, and collaboration. A well-organized project structure makes your code easier to navigate, test, and extend.

Introduction

Project structure refers to how you organize your source code, resources, configuration files, and dependencies. In Kotlin projects, the structure is typically influenced by the build system (usually Gradle or Maven) and follows conventions that make it easy for any developer to understand the codebase.

In this guide, we'll explore:

  • Standard Kotlin project structure
  • Setting up build files
  • Package organization strategies
  • Module separation
  • Resource management
  • Best practices for maintainable project organization

Standard Kotlin Project Structure

A typical Kotlin project follows this basic structure:

project-root/
├── build.gradle.kts (or build.gradle)
├── settings.gradle.kts (or settings.gradle)
├── gradle/
│ └── wrapper/
├── gradlew
├── gradlew.bat
├── src/
│ ├── main/
│ │ ├── kotlin/
│ │ │ └── com/example/project/
│ │ │ ├── models/
│ │ │ ├── services/
│ │ │ └── App.kt
│ │ └── resources/
│ └── test/
│ ├── kotlin/
│ │ └── com/example/project/
│ └── resources/
└── README.md

Let's break down what each part does:

  • build.gradle.kts: The primary build configuration file using Kotlin DSL for Gradle
  • settings.gradle.kts: Defines project settings, including module inclusion
  • gradle/wrapper/: Contains Gradle Wrapper files for consistent build environment
  • gradlew & gradlew.bat: Gradle Wrapper executables for Unix and Windows
  • src/main/kotlin/: Your main source code files go here
  • src/main/resources/: Configuration files, images, and other non-code assets
  • src/test/: Contains test code and test-specific resources

Setting Up the Build File

Your build.gradle.kts file is the central configuration for your project. Here's an example of a basic Kotlin project build file:

kotlin
plugins {
kotlin("jvm") version "1.9.0"
application
}

group = "com.example"
version = "1.0-SNAPSHOT"

repositories {
mavenCentral()
}

dependencies {
implementation(kotlin("stdlib"))

// Testing dependencies
testImplementation(kotlin("test"))
testImplementation("org.junit.jupiter:junit-jupiter:5.9.3")
}

application {
mainClass.set("com.example.project.AppKt")
}

tasks.test {
useJUnitPlatform()
}

This build script:

  1. Applies the Kotlin JVM plugin
  2. Sets group and version information
  3. Configures Maven Central as a dependency source
  4. Adds Kotlin standard library and testing dependencies
  5. Configures the main class for the application
  6. Sets up JUnit for testing

Package Organization Strategies

Organizing packages in your Kotlin project is crucial for maintainability. Here are some common approaches:

By Feature

com.example.project/
├── feature1/
│ ├── Feature1Service.kt
│ ├── Feature1Repository.kt
│ └── models/
├── feature2/
│ ├── Feature2Service.kt
│ └── models/
└── common/
└── utils/

This approach groups code by features, making it easy to understand which files are related to specific functionality.

By Layer

com.example.project/
├── controllers/
├── services/
├── repositories/
├── models/
└── utils/

This traditional approach separates code by its architectural layer.

Example: Creating a Basic Kotlin Project Structure

Let's implement a simple todo application with proper project structure:

1. Set up the directory structure

todo-app/
├── build.gradle.kts
├── settings.gradle.kts
└── src/
├── main/
│ ├── kotlin/
│ │ └── com/example/todo/
│ │ ├── models/
│ │ │ └── Task.kt
│ │ ├── repositories/
│ │ │ └── TaskRepository.kt
│ │ ├── services/
│ │ │ └── TaskService.kt
│ │ └── App.kt
│ └── resources/
│ └── config.properties
└── test/
└── kotlin/
└── com/example/todo/
└── services/
└── TaskServiceTest.kt

2. Create the model class

kotlin
// src/main/kotlin/com/example/todo/models/Task.kt
package com.example.todo.models

data class Task(
val id: Int,
val title: String,
val description: String,
val isCompleted: Boolean = false
)

3. Implement the repository

kotlin
// src/main/kotlin/com/example/todo/repositories/TaskRepository.kt
package com.example.todo.repositories

import com.example.todo.models.Task

class TaskRepository {
private val tasks = mutableListOf<Task>()

fun add(task: Task) {
tasks.add(task)
}

fun getAll(): List<Task> {
return tasks.toList()
}

fun getById(id: Int): Task? {
return tasks.find { it.id == id }
}

fun update(task: Task) {
val index = tasks.indexOfFirst { it.id == task.id }
if (index >= 0) {
tasks[index] = task
}
}

fun delete(id: Int) {
tasks.removeIf { it.id == id }
}
}

4. Create the service layer

kotlin
// src/main/kotlin/com/example/todo/services/TaskService.kt
package com.example.todo.services

import com.example.todo.models.Task
import com.example.todo.repositories.TaskRepository

class TaskService(private val repository: TaskRepository) {

fun createTask(title: String, description: String): Task {
val newId = repository.getAll().maxOfOrNull { it.id }?.plus(1) ?: 1
val task = Task(newId, title, description)
repository.add(task)
return task
}

fun getAllTasks(): List<Task> {
return repository.getAll()
}

fun completeTask(id: Int): Task? {
val task = repository.getById(id) ?: return null
val updatedTask = task.copy(isCompleted = true)
repository.update(updatedTask)
return updatedTask
}

fun deleteTask(id: Int) {
repository.delete(id)
}
}

5. Implement the main application

kotlin
// src/main/kotlin/com/example/todo/App.kt
package com.example.todo

import com.example.todo.repositories.TaskRepository
import com.example.todo.services.TaskService

fun main() {
val repository = TaskRepository()
val service = TaskService(repository)

println("Todo Application")
println("---------------")

// Creating some tasks
service.createTask("Learn Kotlin", "Study Kotlin language features")
service.createTask("Understand project structure", "Learn about proper Kotlin project organization")
service.createTask("Build a project", "Create a sample application with proper structure")

// Completing a task
service.completeTask(1)

// Displaying all tasks
println("\nAll Tasks:")
service.getAllTasks().forEach { task ->
val status = if (task.isCompleted) "✓" else "□"
println("[$status] ${task.id}. ${task.title} - ${task.description}")
}
}

6. Write a test for the service

kotlin
// src/test/kotlin/com/example/todo/services/TaskServiceTest.kt
package com.example.todo.services

import com.example.todo.repositories.TaskRepository
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class TaskServiceTest {

@Test
fun `should create task with correct title and description`() {
// Arrange
val repository = TaskRepository()
val service = TaskService(repository)

// Act
val task = service.createTask("Test Task", "Test Description")

// Assert
assertEquals("Test Task", task.title)
assertEquals("Test Description", task.description)
assertFalse(task.isCompleted)
}

@Test
fun `should mark task as completed`() {
// Arrange
val repository = TaskRepository()
val service = TaskService(repository)
val task = service.createTask("Test Task", "Test Description")

// Act
val completedTask = service.completeTask(task.id)

// Assert
assertTrue(completedTask!!.isCompleted)
}
}

7. Configure the build file

kotlin
// build.gradle.kts
plugins {
kotlin("jvm") version "1.9.0"
application
}

group = "com.example.todo"
version = "1.0-SNAPSHOT"

repositories {
mavenCentral()
}

dependencies {
implementation(kotlin("stdlib"))
testImplementation(kotlin("test"))
}

application {
mainClass.set("com.example.todo.AppKt")
}

tasks.test {
useJUnitPlatform()
}

Multi-Module Project Structure

As your project grows, you might want to split it into multiple modules. Here's an example structure for a multi-module project:

todo-app/
├── build.gradle.kts
├── settings.gradle.kts
├── core/
│ ├── build.gradle.kts
│ └── src/
│ └── main/kotlin/
│ └── com/example/todo/core/
├── api/
│ ├── build.gradle.kts
│ └── src/
│ └── main/kotlin/
│ └── com/example/todo/api/
└── app/
├── build.gradle.kts
└── src/
└── main/kotlin/
└── com/example/todo/app/

Your settings.gradle.kts would include:

kotlin
rootProject.name = "todo-app"
include("core", "api", "app")

Best Practices for Project Structure

  1. Keep packages small: Avoid deeply nested package structures.
  2. Package by feature, not by layer: Group related functionality together.
  3. Use consistent naming: Adopt a naming convention and stick to it.
  4. Create clear module boundaries: Each module should have a single responsibility.
  5. Separate business logic from UI: Keep your domain models independent.
  6. Organize resources properly: Use resource directories effectively.
  7. Leverage build configurations: Use build variants for different environments.

Common Pitfalls to Avoid

  1. Circular dependencies between modules or packages
  2. Inconsistent naming conventions
  3. Monolithic classes that don't follow single responsibility principle
  4. Tight coupling between components
  5. Misplaced files that don't follow the project structure

Summary

A well-organized Kotlin project structure is essential for maintainable and scalable applications. By following these best practices, you can create projects that are:

  • Easy to navigate and understand
  • Modular and decoupled
  • Simple to test
  • Ready for collaboration
  • Prepared for growth

Remember that project structure is not just about directories and files—it's about organizing your code in a way that reflects the architecture and domain of your application. Start with a clean structure from the beginning, and it will pay dividends as your project grows.

Additional Resources

Exercises

  1. Create a new Kotlin project with a proper structure for a library management system.
  2. Convert an existing single-module project into a multi-module structure.
  3. Refactor a package-by-layer structure to a package-by-feature organization.
  4. Add proper test directories and write tests for your domain models.
  5. Create a Gradle multi-platform project structure that targets both JVM and JS.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)