Migration Guide: Koin Annotations (KSP) to Koin Compiler Plugin

May 19, 2026 · View on GitHub

This guide helps you migrate from Koin Annotations 2.x (using KSP) to the Koin Compiler Plugin.

Overview

Koin Annotations (KSP)Koin Compiler Plugin
ProcessingKSP code generationKotlin compiler plugin (FIR + IR)
Generated files*Module.kt files in build/generatedInline transformation, no generated files
Kotlin versionK1 and K2K2 only (2.3.x+)
Koin version3.x, 4.x4.2.0-RC1+

Step 1: Update Build Configuration

Before (KSP) - JVM/Android Only

// build.gradle.kts
plugins {
    id("com.google.devtools.ksp") version "..."
}

dependencies {
    implementation("io.insert-koin:koin-core:...")
    implementation("io.insert-koin:koin-annotations:...")
    ksp("io.insert-koin:koin-ksp-compiler:...")
}

Before (KSP) - KMP Projects (Complex!)

For Kotlin Multiplatform, KSP requires significant boilerplate:

// build.gradle.kts - KMP with KSP
plugins {
    alias(libs.plugins.kotlinMultiplatform)
    alias(libs.plugins.ksp)
}

kotlin {
    androidTarget()
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    sourceSets {
        commonMain.dependencies {
            implementation(libs.koin.core)
            implementation(libs.koin.annotations)
        }
    }

    // KSP: Manual source set configuration for generated files
    sourceSets.named("commonMain").configure {
        kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin")
    }
}

// KSP: Must add compiler to EVERY target separately
dependencies {
    add("kspCommonMainMetadata", libs.koin.ksp.compiler)
    add("kspAndroid", libs.koin.ksp.compiler)
    add("kspIosX64", libs.koin.ksp.compiler)
    add("kspIosArm64", libs.koin.ksp.compiler)
    add("kspIosSimulatorArm64", libs.koin.ksp.compiler)
}

// KSP: Task dependency hack for metadata compilation
tasks.matching { it.name.startsWith("ksp") && it.name != "kspCommonMainKotlinMetadata" }.configureEach {
    dependsOn("kspCommonMainKotlinMetadata")
}

ksp {
    arg("KOIN_CONFIG_CHECK", "true")
}

That's ~25 lines of KSP boilerplate for KMP!

After (Compiler Plugin)

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

dependencies {
    implementation("io.insert-koin:koin-core:4.2.0-RC1")
    implementation("io.insert-koin:koin-annotations:4.2.0-RC1")
}

Remove:

  • KSP plugin (com.google.devtools.ksp)
  • ksp("io.insert-koin:koin-ksp-compiler:...") dependency

Keep:

  • koin-annotations dependency (required for annotations)

Step 2: Annotation Changes

Most annotations work identically. Here are the differences:

Unchanged Annotations

These work exactly the same:

AnnotationStatus
@Module✅ Same
@ComponentScan✅ Same
@Single / @Singleton✅ Same
@Factory✅ Same
@Scoped✅ Same
@KoinViewModel✅ Same
@Named("...")✅ Same
@InjectedParam✅ Same
@Property("...")✅ Same
@Scope(T::class)✅ Same
Custom @Qualifier annotation (no value)⚠️ Behavior change — see note below

Custom Qualifier Annotations — Behavior Change

Annotation classes meta-annotated with @Qualifier (or @Named) that carry no value argument — e.g. @Qualifier annotation class BaseUrl — are now bound as type qualifiers keyed on the annotation class, matching Koin's runtime named<T>() helper.

@Qualifier
annotation class BaseUrl

@Singleton
@BaseUrl
fun provideBaseUrl(): String = "..."

Resolution at runtime:

// Works — reified form resolves to TypeQualifier(BaseUrl::class)
koin.get<String>(named<BaseUrl>())
koin.get<String>(typeQualifier<BaseUrl>())

// No longer works — the plugin does not register a string qualifier for plain custom annotations
koin.get<String>(named("BaseUrl"))

Custom qualifier annotations that carry a value (enum or string arg, e.g. @Dispatcher(IO)) are unchanged — they continue to resolve as string qualifiers keyed on the value.

Package Changes

// Before (KSP) - Android ViewModel annotation
import org.koin.android.annotation.KoinViewModel

// After (Compiler Plugin) - use core annotation
import org.koin.core.annotation.KoinViewModel

@KoinWorker for WorkManager

// Before (KSP) - may not have been supported
// After (Compiler Plugin) - fully supported
@KoinWorker
class MyWorker(
    context: Context,
    params: WorkerParameters,
    val api: ApiService
) : CoroutineWorker(context, params)

Top-Level Function Definitions (New)

The compiler plugin supports definition annotations on top-level functions, discovered by @ComponentScan:

// Top-level functions with annotations
@Singleton
fun provideDatabase(): DatabaseService = PostgresDatabase()

@Factory
fun provideCache(db: DatabaseService): CacheService = RedisCache(db)

@Single
@Named("http")
fun provideHttpClient(): NetworkClient = OkHttpClient()

// Module that scans the package
@Module
@ComponentScan("com.example")
class AppModule
  • Function return type determines the binding type
  • Function parameters are injected as dependencies (like constructor parameters)
  • All parameter annotations work: @Named, @InjectedParam, @Property, nullable, Lazy<T>, List<T>

Step 3: Module Definition Changes

Generated Module Access

// Before (KSP) - reference generated module class
import org.koin.ksp.generated.module

startKoin {
    modules(AppModule().module)
}

// After (Compiler Plugin) - same syntax, but module is extension property
startKoin {
    modules(AppModule().module)
}

Using @KoinApplication (New)

The compiler plugin supports automatic module injection:

// Define app with modules
@KoinApplication(modules = [AppModule::class, NetworkModule::class])
object MyApp

// Modules are auto-injected
fun main() {
    startKoin<MyApp> {
        printLogger()
    }
}

Step 4: DSL Syntax Changes

The compiler plugin introduces a cleaner DSL syntax:

Before (KSP style)

val myModule = module {
    singleOf(::MyService)
    factoryOf(::MyRepository)
    viewModelOf(::MyViewModel)
}

After (Compiler Plugin)

val myModule = module {
    single<MyService>()
    factory<MyRepository>()
    viewModel<MyViewModel>()
}

Both styles define the same dependencies, but the compiler plugin uses reified type parameters instead of constructor references.

Comparison Table

KSP StyleCompiler Plugin Style
singleOf(::MyService)single<MyService>()
factoryOf(::MyRepo)factory<MyRepo>()
viewModelOf(::MyVM)viewModel<MyVM>()
scopedOf(::MyScoped)scoped<MyScoped>()
workerOf(::MyWorker)worker<MyWorker>()

With Qualifiers

// Before
singleOf(::MyService) { named("production") }

// After
single<MyService>(named("production"))

With Options

// Before
singleOf(::MyService) {
    named("production")
    bind<Service>()
    createdAtStart()
}

// After
single<MyService>(named("production")).withOptions {
    bind<Service>()
    createdAtStart()
}

Step 5: Remove Generated Files

After migration, you can delete:

  1. Generated source directories: build/generated/ksp/
  2. Any manual imports from org.koin.ksp.generated

The compiler plugin doesn't generate visible files - transformations happen inline during compilation.

Step 6: Multi-Module Projects

Before (KSP)

Each module needed KSP configuration:

// feature/build.gradle.kts
plugins {
    id("com.google.devtools.ksp")
}

dependencies {
    implementation("io.insert-koin:koin-annotations:1.3.1")
    ksp("io.insert-koin:koin-ksp-compiler:1.3.1")
}

After (Compiler Plugin)

Just apply the plugin:

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

Cross-Module Discovery

Use @Configuration for automatic module discovery across compilation units:

// In feature module
@Module
@ComponentScan
@Configuration
class FeatureModule

// In app module - FeatureModule is auto-discovered
@KoinApplication
object MyApp

startKoin<MyApp>()  // FeatureModule automatically included

Step 7: KMP Migration

The compiler plugin has full KMP support:

// commonMain
@Module
@ComponentScan
class CommonModule {
    @Singleton
    fun provideRepository(): Repository = RepositoryImpl()
}

// Works on all targets: JVM, JS, WASM, iOS, macOS, Linux, Windows

Expect/Actual Classes

// commonMain
@Module
expect class PlatformModule

// androidMain
@Module
actual class PlatformModule {
    @Singleton
    fun providePlatform(): Platform = AndroidPlatform()
}

// iosMain
@Module
actual class PlatformModule {
    @Singleton
    fun providePlatform(): Platform = IosPlatform()
}

Troubleshooting

"Unresolved reference: module"

Make sure you're using Koin 4.2.0-RC1 or later and that you have the koin-annotations dependency. The module extension property is provided by koin-annotations.

Compile errors with K1

The compiler plugin requires K2 (Kotlin 2.3.x+). If you're on an older Kotlin version, you'll need to either:

  1. Upgrade to Kotlin 2.3.x+
  2. Stay on Koin Annotations with KSP

Missing dependencies at runtime

Enable logging to debug:

koinCompiler {
    userLogs = true   // See what's being processed
    debugLogs = true  // Detailed internal logging
}

Benefits of Migration

FeatureKSPCompiler Plugin
Build speedSeparate KSP taskIntegrated in compilation
Generated filesVisible in build/None (inline)
IDE supportDepends on KSP pluginNative Kotlin support
Incremental buildsKSP incrementalKotlin incremental
KMP supportLimitedFull (all targets)
Compile-time safetyPartialFull constructor validation
Top-level functionsNot supportedFully supported

Quick Migration Checklist

  • Update Kotlin to 2.3.x+
  • Update Koin to 4.2.0-RC1+
  • Remove KSP plugin from build.gradle.kts
  • Remove koin-ksp-compiler dependency (keep koin-annotations)
  • Add io.insert-koin.compiler.plugin plugin
  • Update @KoinViewModel import to org.koin.core.annotation
  • Delete build/generated/ksp/ directory
  • Test and verify all dependencies resolve correctly