Skip to main content
Version: 4.2

Koin vs Hilt/Dagger

This page compares Koin with Hilt and Dagger to help you understand the differences and decide which framework fits your needs.

info

Koin supports both DSL and Annotations - choose what fits your team. Both are first-class citizens, equally powerful, and powered by the same Compiler Plugin. This comparison shows annotation examples for fair comparison with Hilt, but Koin's DSL offers equivalent functionality with even less boilerplate.

Philosophy Differences

AspectKoinHilt/Dagger
Learning curveMinutes to learnHours/days to master
Code complexitySimple DSL or annotationsComplex annotation rules
DebuggingClear errors, no generated code mazeGenerated code can be hard to trace
SetupOne plugin, minimal configMultiple annotations, strict rules
Compile safety✅ With Compiler Plugin✅ Always
Runtime flexibility✅ Dynamic features❌ Static only

Annotation Comparison

Even annotations are simpler in Koin:

TaskKoinHilt
Singleton@Singleton class MyService@Singleton class MyService @Inject constructor(...)
Interface bindingAutomaticRequires @Binds in abstract module
Component scanning@ComponentScan("package")Not available
Module discovery@Configuration - auto-discoveredManual @InstallIn per module
Provide 3rd party@Singleton fun provide()@Provides in @Module + @InstallIn
ViewModel@KoinViewModel class MyVM@HiltViewModel class MyVM @Inject constructor

Code Comparison

Simple Singleton

Koin:

@Singleton
class MyRepository(val api: ApiService)

@Module
@ComponentScan("com.app")
class AppModule

Hilt:

@Singleton
class MyRepository @Inject constructor(val api: ApiService)

@Module
@InstallIn(SingletonComponent::class)
abstract class AppModule {
@Binds
abstract fun bindRepository(impl: MyRepository): Repository
}

Interface Binding

Koin - Automatic:

@Singleton
class UserRepositoryImpl(val db: Database) : UserRepository

// That's it! Koin automatically binds to UserRepository interface

Hilt - Requires explicit binding:

@Singleton
class UserRepositoryImpl @Inject constructor(val db: Database) : UserRepository

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
@Binds
abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
}

Multi-Module Apps

Koin - Modules auto-discovered:

// feature/auth/AuthModule.kt
@Module
@ComponentScan
@Configuration // Auto-discovered!
class AuthModule

// feature/profile/ProfileModule.kt
@Module
@ComponentScan
@Configuration // Auto-discovered!
class ProfileModule

// app/MyApp.kt
@KoinApplication // No need to list modules
class MyApp

Hilt - Must manually install each module:

// feature/auth/AuthModule.kt
@Module
@InstallIn(SingletonComponent::class)
class AuthModule { ... }

// feature/profile/ProfileModule.kt
@Module
@InstallIn(SingletonComponent::class)
class ProfileModule { ... }

// app/MyApp.kt
@HiltAndroidApp
class MyApp // Still need correct @InstallIn everywhere

ViewModel

Koin:

@KoinViewModel
class UserViewModel(
private val repository: UserRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel()

// In Activity/Fragment
val viewModel: UserViewModel by viewModel()

// In Compose
val viewModel: UserViewModel = koinViewModel()

Hilt:

@HiltViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository,
private val savedStateHandle: SavedStateHandle
) : ViewModel()

// In Activity/Fragment
val viewModel: UserViewModel by viewModels()

// In Compose
val viewModel: UserViewModel = hiltViewModel()

Providing Third-Party Libraries

Koin:

@Module
class NetworkModule {
@Singleton
fun provideRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()

@Singleton
fun provideApi(retrofit: Retrofit): ApiService =
retrofit.create(ApiService::class.java)
}

Hilt:

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Provides
@Singleton
fun provideRetrofit(): Retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()

@Provides
@Singleton
fun provideApi(retrofit: Retrofit): ApiService =
retrofit.create(ApiService::class.java)
}

Dynamic Features: Koin's Unique Advantage

Koin is runtime-based but performant and compile-safe. This enables dynamic features that Hilt cannot offer:

Dynamic FeatureKoinHilt
Load modules at runtimeloadKoinModules()❌ Not possible
Unload modulesunloadKoinModules()❌ Not possible
Lazy background loadinglazyModules()❌ Not possible
Feature flag injection✅ Easy⚠️ Complex workarounds
Plugin architectures✅ Natural fit❌ Very difficult
A/B test implementations✅ Runtime swap⚠️ Compile-time only
Dynamic configuration✅ Supported❌ Must recompile

Example: Dynamic Module Loading

// KOIN - Dynamic module loading
if (userHasPremium) {
loadKoinModules(premiumFeatureModule)
}

// Later, if subscription expires
unloadKoinModules(premiumFeatureModule)

// Lazy loading for faster startup
startKoin {
modules(coreModule)
lazyModules(
analyticsModule, // Loaded in background
heavyFeatureModule
)
}

This is impossible with Hilt - all dependencies are wired at compile time.

Example: Feature Flags

// KOIN - Switch implementations at runtime
val featureModule = module {
if (FeatureFlags.useNewApi) {
single<ApiService> { NewApiService() }
} else {
single<ApiService> { LegacyApiService() }
}
}

// Or dynamically
fun updateApiImplementation(useNew: Boolean) {
unloadKoinModules(apiModule)
loadKoinModules(if (useNew) newApiModule else legacyApiModule)
}

Setup Comparison

Koin Setup

// settings.gradle.kts
plugins {
id("io.insert-koin.compiler.plugin") version "1.0.0" apply false
}

// app/build.gradle.kts
plugins {
id("io.insert-koin.compiler.plugin")
}

dependencies {
implementation(platform("io.insert-koin:koin-bom:4.2.0"))
implementation("io.insert-koin:koin-android")
}

Hilt Setup

// settings.gradle.kts
plugins {
id("com.google.dagger.hilt.android") version "2.x" apply false
}

// app/build.gradle.kts
plugins {
id("com.google.devtools.ksp")
id("dagger.hilt.android.plugin")
}

dependencies {
implementation("com.google.dagger:hilt-android:2.x")
ksp("com.google.dagger:hilt-compiler:2.x")
}

Error Messages

Koin

org.koin.core.error.NoBeanDefFoundException:
No definition found for class 'com.app.UserRepository'.
Check your module definitions.

Clear, points directly to the issue.

Hilt/Dagger

error: [Dagger/MissingBinding] com.app.UserRepository cannot be provided
without an @Inject constructor or an @Provides-annotated method.
com.app.UserRepository is injected at
com.app.UserService(repository)
com.app.UserService is injected at
com.app.UserActivity.service
com.app.UserActivity is injected at
dagger.hilt.android.internal.managers.ActivityComponentManager.inject

Longer, requires understanding the component graph.

When to Choose Each

Choose Koin When:

  • You value productivity and simplicity
  • You need runtime flexibility (dynamic modules, feature flags)
  • You're building Kotlin Multiplatform apps
  • Your team wants to learn quickly
  • You prefer less boilerplate
  • You want easier debugging

Choose Hilt When:

  • Your team already knows Dagger
  • You need Google-first ecosystem compatibility
  • You require Dagger's specific features
  • You prefer strict compile-time-only validation

Migration from Hilt to Koin

If you're considering migration:

Concept Mapping

HiltKoin
@HiltAndroidAppstartKoin { }
@AndroidEntryPointby inject() / by viewModel()
@HiltViewModel@KoinViewModel
@Inject constructorJust constructor (auto-detected)
@BindsAutomatic or bind
@InstallIn(SingletonComponent)@Configuration
@Provides@Singleton fun provide()

Gradual Migration

You can migrate incrementally:

  1. Add Koin to your project
  2. Migrate one feature module at a time
  3. Both DI frameworks can coexist during transition
  4. Remove Hilt when migration is complete

See Migrating from Hilt for detailed steps.

Summary

Koin: Simple AND Powerful

  • Compile-time safety like Hilt (with Compiler Plugin)
  • DSL or Annotations - both equally powerful, your choice
  • Simplicity and productivity that Hilt can't match
  • Dynamic runtime features that Hilt can't do

You don't have to choose between safety and simplicity. With Koin, you get both.

Next Steps