JSR-330 Annotations
The Koin Compiler Plugin reads the standard JSR-330 annotations from jakarta.inject and javax.inject. A class with @Singleton or an @Inject constructor becomes a Koin definition, with no Koin annotation on it. This page lists what is supported and how to set it up. It applies to the annotations style: you need the Compiler Plugin and a module with @ComponentScan (see Modules).
The main use is code shared with another JSR-330 framework, or a migration from Hilt or Dagger where you keep existing classes unchanged (see Migrating from Hilt).
Setup
On the JVM and Android, keep the JSR-330 artifact you already use:
dependencies {
implementation("jakarta.inject:jakarta.inject-api:2.0.1")
// or the legacy package
implementation("javax.inject:javax.inject:1")
}
In Kotlin Multiplatform code, these Java artifacts are not available. Add koin-jsr330, which provides the jakarta.inject annotations for every Kotlin target:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.insert-koin:koin-jsr330:$koin_annotations_version")
}
}
}
koin-jsr330 is published with Koin Annotations, not with the Koin BOM. Use the Koin Annotations version.
Supported annotations
| JSR-330 annotation | Koin equivalent | Applies to |
|---|---|---|
@Singleton | @Singleton / @Single | Class, function in a @Module |
@Inject on a constructor | @Factory | Class |
@Named("x") | @Named("x") | Class, function, parameter |
@Qualifier | @Qualifier | Your own qualifier annotation class |
Both packages work: jakarta.inject.* and javax.inject.*. Koin annotations on the same class take precedence. For example @KoinViewModel on a class with an @Inject constructor declares a ViewModel, not a factory.
JSR-330 @Scope has no effect on Koin scopes. Use Koin's @Scope and @Scoped, or a scope archetype such as @ActivityScope (see Scopes).
Definitions
@Singleton declares a single. A class with an @Inject constructor and no other annotation declares a factory:
import jakarta.inject.Inject
import jakarta.inject.Singleton
@Singleton
class UserRepository @Inject constructor(
private val api: ApiService,
private val database: UserDatabase,
)
// a new instance for each request
class GetUserUseCase @Inject constructor(
private val repository: UserRepository,
)
@Inject is optional when the class has a @Singleton annotation: Koin uses the primary constructor. When a class has several constructors, Koin uses the one annotated with @Inject.
These classes must be in a package covered by @ComponentScan, like any annotated class:
@Module
@ComponentScan("com.example.data")
class DataModule
@Singleton also works on provider functions in a @Module class, for objects you build yourself:
@Module
class NetworkModule {
@Singleton
fun retrofit(): Retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
}
Binding to supertypes works as with Koin annotations: the class is bound to its interfaces (see Definitions).
Qualifiers
@Named works on the declaration and on the injection point:
import jakarta.inject.Inject
import jakarta.inject.Named
import jakarta.inject.Singleton
@Singleton
@Named("inMemory")
class InMemoryCache @Inject constructor() : Cache
@Singleton
@Named("disk")
class DiskCache @Inject constructor(private val context: Context) : Cache
@Singleton
class ImageLoader @Inject constructor(
@Named("inMemory") private val memoryCache: Cache,
@Named("disk") private val diskCache: Cache,
)
Custom qualifier annotations marked with JSR-330 @Qualifier work like Koin's custom qualifiers:
import jakarta.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IoDispatcher
@Module
class DispatcherModule {
@Singleton
@IoDispatcher
fun ioDispatcher(): CoroutineDispatcher = Dispatchers.IO
}
@Singleton
class SyncManager @Inject constructor(
@IoDispatcher private val dispatcher: CoroutineDispatcher,
)
See Qualifiers for how qualifiers are resolved.
Mixing with Koin annotations
You can use JSR-330 and Koin annotations in the same project, and on the same class:
@Singleton // jakarta.inject
@Named("local")
class LocalDatabase @Inject constructor() : Database
@Single // Koin
@Named("remote")
class RemoteDatabase(private val api: ApiService) : Database
@Factory // Koin, with a JSR-330 constructor
class DatabaseManager @Inject constructor(
@Named("local") private val local: Database,
@Named("remote") private val remote: Database,
)
JSR-330 has no annotation for ViewModels, workers or injected parameters. Use the Koin annotations for them: @KoinViewModel, @KoinWorker, @InjectedParam. A Context parameter needs no qualifier: Koin passes the context given to androidContext().
@KoinViewModel
class UserViewModel @Inject constructor(
private val repository: UserRepository,
private val savedStateHandle: SavedStateHandle,
) : ViewModel()
Limits
- Field and method injection:
@Inject lateinit varfields and@Injectmethods are not supported. Inject through the constructor, or useby inject()in Android components (see Entry Points). Provider<T>: ajakarta.inject.Provider<T>parameter is not resolved. Use aLazy<T>parameter, or the provider pattern in Advanced Patterns.- Annotations style only:
@Singletonand@Injectdeclare definitions only through@ComponentScan. In a DSL module you still writesingle<T>(). The Compiler Plugin DSL then reads@Namedon parameters and uses the@Injectconstructor. - Compile-time safety: JSR-330 definitions are checked by the Compiler Plugin like Koin annotations (see Compile-Time Safety).
Related
- Migrating from Hilt: step-by-step migration, where JSR-330 lets you keep existing classes.
- Definitions: definition types and interface binding.
- Qualifiers: named, type and custom qualifiers.
- Annotations Reference: every Koin annotation.
- Compiler Plugin Setup: install the plugin.