Dependency Injection Containers for Kotlin: Full Overview and Comparison
Dependency Injection (DI) is a key pattern in modern development that allows you to create loosely coupled, testable, and scalable applications. In the Kotlin ecosystem, there are several powerful DI containers, each with its own features. In this article, we will take a detailed look at the three most popular solutions: Koin, Kodein-DI, and Dagger/Hilt. You will learn how they work, their strengths and weaknesses, and which tool to choose for your project.
What is a Dependency Injection Container?
A DI container is a framework that manages the creation and lifecycle of objects (dependencies) and automatically injects them into classes. Instead of manually creating dependencies via a constructor or factory, you describe the assembly rules in modules, and the container handles all the routine work. This is especially useful in large projects with hundreds of classes.
1. Koin — Lightweight and Simple DI for Kotlin
Koin is a DSL-oriented DI container written in pure Kotlin. It does not require code generation, reflection, or annotations. All configuration is done through a convenient DSL (Domain Specific Language).
Installation
// build.gradle.ktsimplementation("io.insert-koin:koin-core:3.5.3")Usage Example
// Define dependenciesval appModule = module { single { HttpClient() } factory { UserRepository(get()) }}
// Start Koinfun main() { startKoin { modules(appModule) } // Get dependency val repo: UserRepository by inject()}
class UserRepository(private val client: HttpClient)Pros:
- Minimal learning curve — no annotations, no code generation
- Easy integration with Android (Koin Android)
- Support for scopes (Scope API)
- Fast compilation
Cons:
- Errors are only detected at runtime
- No compile-time dependency graph validation
- Slightly slower than Dagger at runtime due to reflection
2. Kodein-DI — Flexible and Multiplatform
Kodein-DI is another DI framework written in pure Kotlin. It offers advanced features: support for multiplatform projects (KMP), lazy initialization, dependency tagging, and built-in factories.
Configuration Example
val di = DI { bind<ApiService>() with singleton { RetrofitApiService() } bind<Logger>() with provider { ConsoleLogger() } bind<UserRepository>() with factory { UserRepositoryImpl(instance()) }}
// Usageclass MainActivity { private val repo: UserRepository by di.instance()}Pros:
- Full support for Kotlin Multiplatform (iOS, JS, JVM)
- Powerful system of tags and modifiers
- Ability to create nested containers
- Support for scopes and thread safety
Cons:
- Less popular than Koin (smaller community and fewer tutorials)
- DSL may feel overloaded for simple projects
- Also runtime dependency checking
3. Dagger / Hilt — High-Performance DI from Google
Dagger and its Android wrapper Hilt are DI frameworks based on compile-time code generation. They use annotations (@Inject, @Module, @Component) and generate all the binding code in advance. This provides maximum runtime performance.
Example with Hilt
@HiltAndroidAppclass MyApplication : Application()
@AndroidEntryPointclass MainActivity : AppCompatActivity() { @Inject latein