docs/intro/koin-compiler-plugin.md
The Koin Compiler Plugin is the recommended approach for all new Kotlin 2.x projects. It's a native Kotlin compiler plugin that powers both DSL and Annotations with auto-wiring, compile-time safety, and cleaner syntax.
The Koin Compiler Plugin is a native Kotlin Compiler Plugin (K2) - not KSP or annotation processing. It integrates directly with the Kotlin compiler to:
get() calls neededThe plugin auto-detects constructor dependencies, reducing manual wiring errors:
// Without Compiler Plugin - easy to make mistakes
val appModule = module {
single { UserService(get(), get(), get()) } // Hope you got the order right!
}
// With Compiler Plugin - auto-wired
val appModule = module {
single<UserService>() // Plugin detects all constructor parameters
}
Less boilerplate, more readable:
| Classic DSL | Compiler Plugin DSL |
|---|---|
singleOf(::MyService) | single<MyService>() |
single { MyService(get(), get()) } | single<MyService>() |
factoryOf(::MyRepo) | factory<MyRepo>() |
viewModelOf(::MyVM) | viewModel<MyVM>() |
scopedOf(::MyPresenter) | scoped<MyPresenter>() |
workerOf(::MyWorker) | worker<MyWorker>() |
The Koin Compiler Plugin provides compile-time dependency verification for both DSL and Annotations:
startKoin<T>()get<T>(), inject<T>(), koinViewModel<T>() callIf it compiles, every dependency and every injection call site is satisfied. This replaces verify() and checkModules() — no runtime test harness needed.
See Compile-Time Safety for full details.
Use whichever style you prefer - the same plugin powers both with identical capabilities:
DSL Style:
val appModule = module {
single<Database>()
single<UserRepository>()
viewModel<UserViewModel>()
}
:::info DSL + Parameter Annotations When using DSL style, you still use parameter annotations on your classes to guide the plugin:
class UserPresenter(
@InjectedParam val userId: String, // Runtime parameter
@Named("api") val client: ApiClient, // Qualified dependency
val repository: UserRepository // Auto-resolved
)
val appModule = module {
factory<UserPresenter>() // Plugin reads annotations from the class
}
The DSL defines where dependencies are registered. Parameter annotations define how they are resolved. :::
Annotation Style:
@Singleton
class Database
@Singleton
class UserRepository(private val database: Database)
@KoinViewModel
class UserViewModel(private val repository: UserRepository) : ViewModel()
Add the Compiler Plugin to your project.
:::info See the Compiler Plugin Setup Guide for detailed instructions. :::
Import from the compiler plugin package:
import org.koin.plugin.module.dsl.*
import org.koin.dsl.module
val appModule = module {
single<Database>()
single<ApiClient>()
single<UserRepository>()
viewModel<UserViewModel>()
}
:::note
The Compiler Plugin DSL is in org.koin.plugin.module.dsl. Classic DSL remains in org.koin.dsl.
:::
Annotations work the same as before:
@Singleton
class Database
@Singleton
class ApiClient
@Singleton
class UserRepository(
private val database: Database,
private val apiClient: ApiClient
)
@KoinViewModel
class UserViewModel(private val repository: UserRepository) : ViewModel()
@Module
@ComponentScan("com.myapp")
class AppModule
The Compiler Plugin operates in two phases:
During the Frontend Intermediate Representation phase, the plugin:
During the Intermediate Representation phase, the plugin:
get() calls for each parameter@Named)@InjectedParam)When you write:
single<UserRepository>()
The plugin transforms it to:
single { UserRepository(get(), get()) } // Parameters auto-detected
For more complex cases:
// Your code
@Singleton
class MyService(
val required: RequiredDep,
val optional: OptionalDep?,
@Named("special") val named: NamedDep,
val lazy: Lazy<LazyDep>,
@InjectedParam val param: String
)
The plugin generates proper handling for each parameter type:
get()getOrNull()get(named("special"))inject()params.get()import org.koin.plugin.module.dsl.*
val appModule = module {
// Singleton - one instance
single<MyService>()
// Factory - new instance each time
factory<MyPresenter>()
// Scoped - instance per scope
scope<MyActivity> {
scoped<ActivityPresenter>()
}
// ViewModel
viewModel<MyViewModel>()
// Worker (Android WorkManager)
worker<MyWorker>()
}
create()Use create(::T) inside a definition lambda to safely build an instance with auto-resolved constructor dependencies:
val appModule = module {
single { create(::MyService) }
}
The compiler plugin transforms create(::MyService) into MyService(get(), get(), ...), auto-wiring all constructor parameters.
Use @Named on your classes to define qualifiers, and on parameters to specify which dependency to inject:
// Define implementations with @Named qualifier
@Named("local")
class LocalDatabase : Database
@Named("remote")
class RemoteDatabase : Database
// Use @Named on parameters to specify which one to inject
class SyncService(
@Named("local") val localDb: Database,
@Named("remote") val remoteDb: Database
)
// DSL - plugin reads @Named from classes and parameters
val appModule = module {
single<LocalDatabase>()
single<RemoteDatabase>()
single<SyncService>()
}
You can also create custom qualifiers with @Qualifier:
@Qualifier
annotation class LocalDb
@Qualifier
annotation class RemoteDb
@LocalDb
class LocalDatabase : Database
@RemoteDb
class RemoteDatabase : Database
class SyncService(
@LocalDb val localDb: Database,
@RemoteDb val remoteDb: Database
)
Use @InjectedParam on your class to mark parameters passed at injection time:
// Annotation on the class - tells the plugin how to handle this parameter
class UserPresenter(
@InjectedParam val userId: String, // Passed via parametersOf()
val repository: UserRepository // Auto-resolved by Koin
)
// DSL in module - tells Koin where to register
val appModule = module {
factory<UserPresenter>()
}
// Usage - pass the runtime parameter
val presenter: UserPresenter = get { parametersOf("user123") }
val appModule = module {
single<UserRepositoryImpl>() bind UserRepository::class
// Or multiple bindings
single<MyServiceImpl>() binds arrayOf(
ServiceA::class,
ServiceB::class
)
}
| Annotation | Description |
|---|---|
@Singleton / @Single | Single instance |
@Factory | New instance each time |
@Scoped | Instance per scope |
@KoinViewModel | Android ViewModel |
@KoinWorker | Android WorkManager Worker |
| Annotation | Description |
|---|---|
@Named("qualifier") | Named qualifier |
@InjectedParam | Runtime parameter (via parametersOf()) |
@Property("key") | Koin property value |
@Provided | External dependency (skip validation) |
| Annotation | Description |
|---|---|
@Module | Declares a Koin module |
@ComponentScan("package") | Scan package for annotated classes |
@Configuration | Auto-discovered module |
| Approach | Status | Package | Syntax |
|---|---|---|---|
| Compiler Plugin DSL | Recommended | Already located in Koin org.koin.plugin.module.dsl | single<MyService>(), factory<MyRepo>(), viewModel<MyVM>() |
| Compiler Plugin Annotations | Recommended | Annotations available in koin-annotations | @Singleton, @Factory, @KoinViewModel |
| Classic DSL | Fully Supported | org.koin.dsl | singleOf(::MyService), single { MyService(get()) }, viewModelOf(::MyVM) |
| KSP Processor | Deprecated | koin-ksp-compiler | Legacy processor for Koin Annotations — same annotations, Migrate to Compiler Plugin ⚠️ |
koin-ksp-compiler (Deprecated)koin-annotations library is not deprecated — it's now part of the Koin projectkoin-ksp-compiler) is deprecatedkoin-ksp-compiler will be removed in a future Koin versionIf you're using classic DSL, migration is optional but recommended:
org.koin.plugin.module.dsl.*singleOf(::Class) with single<Class>()get() callsSee the Compiler Plugin DSL reference for the compile-time safe syntax.
koin-ksp-compiler)If you're using Koin Annotations with the legacy KSP processor, migration is recommended now:
koin-ksp-compiler with the Koin Compiler PluginSee Migrating from KSP to Compiler Plugin.
// build.gradle.kts
koinCompiler {
// Options will be documented here
}
The Compiler Plugin doesn't replace Classic DSL - it adds analysis and generation on top. Classic DSL remains fully supported:
// Still works perfectly
val appModule = module {
singleOf(::Database)
singleOf(::ApiClient)
single { CustomService(get(), get(), configValue) } // Custom logic
viewModelOf(::UserViewModel)
}
Use Classic DSL when you need:
getOrNull() for optional dependencies