Skip to main content
Version: 4.2

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")
}
}
}
note

koin-jsr330 is published with Koin Annotations, not with the Koin BOM. Use the Koin Annotations version.

Supported annotations​

JSR-330 annotationKoin equivalentApplies to
@Singleton@Singleton / @SingleClass, function in a @Module
@Inject on a constructor@FactoryClass
@Named("x")@Named("x")Class, function, parameter
@Qualifier@QualifierYour 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 var fields and @Inject methods are not supported. Inject through the constructor, or use by inject() in Android components (see Entry Points).
  • Provider<T>: a jakarta.inject.Provider<T> parameter is not resolved. Use a Lazy<T> parameter, or the provider pattern in Advanced Patterns.
  • Annotations style only: @Singleton and @Inject declare definitions only through @ComponentScan. In a DSL module you still write single<T>(). The Compiler Plugin DSL then reads @Named on parameters and uses the @Inject constructor.
  • Compile-time safety: JSR-330 definitions are checked by the Compiler Plugin like Koin annotations (see Compile-Time Safety).