Back to Koin

JSR-330 Compatibility

docs/reference/koin-android/jsr330.md

4.2.16.2 KB
Original Source

Koin provides full JSR-330 (Jakarta Inject) compatibility through Koin Annotations and the Kotlin Compiler Plugin. This makes Koin an excellent choice for teams migrating from Hilt, Dagger, or other JSR-330 compatible frameworks.

Why JSR-330?

JSR-330 defines a standard set of annotations for dependency injection in Java/Kotlin:

JSR-330 AnnotationKoin EquivalentPurpose
@InjectConstructor detectionMark injectable constructors
@Singleton@SingleSingle instance scope
@Named@NamedString-based qualifier
@Qualifier@QualifierCustom qualifier annotations
@Scope@ScopeCustom scope annotations

Setup

Add the koin-jsr330 dependency to your project:

kotlin
dependencies {
    implementation("io.insert-koin:koin-jsr330:$koin_version")
}

Basic Usage

@Singleton and @Inject

Your existing Hilt/Dagger classes work with Koin:

kotlin
import jakarta.inject.Inject
import jakarta.inject.Singleton

@Singleton
class UserRepository @Inject constructor(
    private val api: ApiService,
    private val database: UserDatabase
)

@Singleton
class ApiService @Inject constructor(
    private val httpClient: OkHttpClient
)

@Named Qualifiers

String-based qualifiers work identically:

kotlin
import jakarta.inject.Named
import jakarta.inject.Singleton

@Singleton
@Named("api")
class ApiOkHttpClient @Inject constructor() : OkHttpClient()

@Singleton
@Named("image")
class ImageOkHttpClient @Inject constructor() : OkHttpClient()

@Singleton
class NetworkManager @Inject constructor(
    @Named("api") private val apiClient: OkHttpClient,
    @Named("image") private val imageClient: OkHttpClient
)

Custom @Qualifier Annotations

Keep your existing custom qualifiers:

kotlin
import jakarta.inject.Qualifier

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class IoDispatcher

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class MainDispatcher

@Singleton
class CoroutineModule {
    @IoDispatcher
    fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO

    @MainDispatcher
    fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main
}

@Singleton
class DataSyncManager @Inject constructor(
    @IoDispatcher private val ioDispatcher: CoroutineDispatcher
)

Android ViewModel

JSR-330 annotations work with Koin's ViewModel support:

kotlin
import jakarta.inject.Inject

@KoinViewModel
class UserViewModel @Inject constructor(
    private val userRepository: UserRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    fun loadUser(id: String) {
        viewModelScope.launch {
            val user = userRepository.getUser(id)
            // ...
        }
    }
}

:::note Use @KoinViewModel (Koin annotation) for ViewModels. JSR-330 doesn't define a ViewModel annotation. :::

Migration from Hilt

Before (Hilt)

kotlin
@HiltAndroidApp
class MyApplication : Application()

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject lateinit var analytics: Analytics
}

@Singleton
class Analytics @Inject constructor(
    @ApplicationContext private val context: Context
)

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com")
        .build()
}

After (Koin with JSR-330)

kotlin
// Application - use startKoin
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin {
            androidContext(this@MyApplication)
            modules(appModule)
        }
    }
}

// Activity - use by inject()
class MainActivity : AppCompatActivity() {
    private val analytics: Analytics by inject()
}

// Keep @Singleton and @Inject unchanged!
@Singleton
class Analytics @Inject constructor(
    private val context: Context  // Koin provides Context automatically
)

// Module with @Single for provided instances
@Module
@ComponentScan
class NetworkModule {
    @Single
    fun provideRetrofit(): Retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com")
        .build()
}

Mixed Usage

You can freely mix JSR-330 and Koin annotations in the same project:

kotlin
// JSR-330 style
@Singleton
@Named("local")
class LocalDatabase @Inject constructor() : Database

// Koin style
@Single
@Named("remote")
class RemoteDatabase(private val api: ApiService) : Database

// Mixed - Koin annotation with JSR-330 injection
@Factory
class DatabaseManager @Inject constructor(
    @Named("local") private val local: Database,
    @Named("remote") private val remote: Database
)

Compiler Plugin Support

The Koin Compiler Plugin also recognizes JSR-330 annotations:

kotlin
// These are equivalent with Compiler Plugin
@Singleton
class UserRepository @Inject constructor(private val api: ApiService)

// Compiler Plugin DSL (generates same code)
val module = module {
    single<UserRepository>()
}

Benefits

  • Zero code changes - Existing @Inject and @Singleton annotations work as-is
  • Gradual migration - Migrate module by module, mixing Hilt and Koin
  • Standard compliance - JSR-330 is a Java/Kotlin standard, not framework-specific
  • Team familiarity - Developers know these annotations from Hilt/Dagger
  • Tooling support - IDE support for JSR-330 annotations

Limitations

A few Hilt-specific features don't have direct JSR-330 equivalents:

Hilt FeatureKoin Approach
@HiltAndroidApp@KoinApplication + startKoin<T>{} in Application
@AndroidEntryPointby inject() extension functions
@HiltViewModel@KoinViewModel
@ApplicationContextContext parameter (auto-injected)
@ActivityContextScope-based context resolution

Next Steps