Choosing your style
Koin offers three ways to declare definitions: the Compiler Plugin DSL, annotations, and the classic DSL. They all fill the same container, so the choice changes how you write modules, not how Koin works at runtime. This page shows each style, its requirements and tradeoffs, and recommends a default. It assumes you know what a definition and a module are (see Core Concepts).
The three styles side by side
The same module in each style:
- Compiler Plugin DSL
- Annotations
- Classic DSL
import org.koin.dsl.module
import org.koin.plugin.module.dsl.*
val appModule = module {
single<ApiService>()
single<UserRepositoryImpl>() bind UserRepository::class
viewModel<UserViewModel>()
}
You name the type. The Koin Compiler Plugin reads its constructor and generates the wiring.
@Singleton
class ApiService
@Singleton
class UserRepositoryImpl(private val api: ApiService) : UserRepository
@KoinViewModel
class UserViewModel(private val repository: UserRepository) : ViewModel()
@Module
@ComponentScan("com.example.app")
class AppModule
You annotate each class. @ComponentScan gathers them into a module, and the Compiler Plugin generates the wiring. Interfaces are bound automatically.
import org.koin.dsl.module
import org.koin.core.module.dsl.*
val appModule = module {
singleOf(::ApiService)
singleOf(::UserRepositoryImpl) bind UserRepository::class
viewModelOf(::UserViewModel)
}
Or with the constructor call written by hand:
val appModule = module {
single { ApiService() }
single<UserRepository> { UserRepositoryImpl(get()) }
viewModel { UserViewModel(get()) }
}
No compiler plugin. Koin runs the lambdas at runtime.
Requirements
| Compiler Plugin DSL | Annotations | Classic DSL | |
|---|---|---|---|
| Koin version | 4.2.0+ | 4.2.0+ | Any |
| Kotlin version | 2.3.20+ (K2) | 2.3.20+ (K2) | Any version supported by your Koin version |
| Gradle plugin | io.insert-koin.compiler.plugin | io.insert-koin.compiler.plugin | None |
| Extra dependency | koin-annotations only for parameter annotations (@Named, @InjectedParam) | koin-annotations | None |
| Imports | org.koin.plugin.module.dsl.* | org.koin.core.annotation.* | org.koin.core.module.dsl.* |
Each Compiler Plugin release is verified on a list of Kotlin versions. Plugin 1.2.1 is verified on Kotlin 2.3.20, 2.4.0, 2.4.10 and 2.4.20. Installation steps are in Compiler Plugin Setup.
The Compiler Plugin DSL functions (single<T>(), factory<T>(), create(::T)) are stubs. Without the Gradle plugin applied, the code compiles but throws NotImplementedError at runtime. Apply the plugin in every Gradle module that uses them.
Tradeoffs
Compiler Plugin DSL
- Compile-time checks: the full graph and every
get()call site are validated at build time. - No wiring code: no
get(), no constructor references. Adding a constructor parameter needs no module change. - Explicit modules: you see every definition of a module in one place, and you can use Kotlin code (conditions, loops) around them.
- Parameter annotations: qualifiers and injected parameters are declared on the class (
@Named,@InjectedParam), so the class depends onkoin-annotations. - Kotlin coupling: you need a recent K2 compiler, and a new Kotlin release may need a new plugin release.
Annotations
- Compile-time checks: same as the Compiler Plugin DSL.
- Definition next to the class: the lifetime and bindings are visible where the class is written. Teams coming from Hilt, Dagger or Spring find the model familiar.
- Automatic binding: a class is bound to all its supertypes unless you list
binds. - Scanning configuration: definitions are found by package. You need
@Moduleand@ComponentScanwith the right packages, and@Configurationor@KoinApplicationto assemble modules across Gradle modules. A class outside the scanned packages is not part of the module. The compile-time check reports it only if another definition needs it. - Gaps: some options exist only in the DSL, such as the
onClosecallback. You write those definitions in a DSL module. - Kotlin coupling: same as the Compiler Plugin DSL.
Classic DSL
- Works everywhere: no compiler plugin, no Kotlin version constraint beyond Koin's own.
- Full control: the lambda is plain Kotlin, so you can call builders, read configuration or choose an implementation at runtime.
- Runtime errors: without the plugin, a missing definition is found when the code runs. Use
verify()in a unit test to catch it earlier. - Manual wiring:
single { UserRepository(get(), get()) }must follow constructor changes by hand.singleOf(::UserRepository)follows them, but needs explicit calls for nullable, lazy or qualified parameters.
If you apply the Compiler Plugin to a classic DSL project, singleOf(::T), factoryOf(::T), scopedOf(::T) and viewModelOf(::T) are checked at build time too, and hand-written lambdas are checked through their get() calls. This is a way to get compile-time checks without rewriting your modules.
Which one to pick
For a new project on Kotlin 2.3.20 or later, use the Compiler Plugin DSL. It gives compile-time checks and keeps modules as explicit Kotlin code. If your team prefers to declare definitions on the class, use annotations: they get the same checks.
Keep the classic DSL if you can't use a recent Kotlin version, if you build a library that must not require a compiler plugin from its users, or if your existing modules work and you don't want to change them. You can still add the Compiler Plugin later for checks.
Mixing styles
All three styles produce the same definitions, so one project can use several. Common cases:
- Compiler Plugin DSL and classic DSL in one module: write
single<T>()for most definitions, and a lambda where you need custom logic. - Annotations with a few DSL modules: start from your
@KoinApplicationclass and add DSL modules in the configuration block. - DSL start with annotated modules: load a
@Moduleclass into a regularstartKoin.
import org.koin.plugin.module.dsl.module
import org.koin.plugin.module.dsl.startKoin
// annotated application, plus DSL modules
startKoin<MyApp> {
modules(legacyModule)
}
// DSL application, plus an annotated module
startKoin {
modules(appModule)
module<NetworkModule>()
}
@Module(includes = [...]) and @KoinApplication(modules = [...]) only accept @Module classes, not DSL module { } values. See Modules and Starting Koin for the full rules.
The KSP processor is deprecated
Before the Compiler Plugin, annotations were processed by koin-ksp-compiler, a KSP processor. That processor is deprecated and will be removed in a future Koin version. The annotations themselves are not deprecated: koin-annotations is part of the main Koin project and ships with each Koin version. If you use KSP today, your annotated classes stay the same and only the build setup and the start code change. See Migrating from KSP to the Compiler Plugin.
Tabs on every page
Code samples in these docs use the same three tabs: Compiler Plugin DSL, Annotations, Classic DSL. When you select a tab, the choice is saved in your browser and applied on every page. Samples that are the same in all styles, such as by inject() in an Activity, are shown once without tabs.
Related
- Koin Compiler Plugin: how the plugin works and what it validates.
- Compiler Plugin Setup: install the Gradle plugin.
- Definitions: every definition type, in the three styles.
- DSL Reference and Annotations Reference: every keyword and annotation.
- Migrating from KSP: move from
koin-ksp-compiler.