Skip to main content
Version: 4.2

Qualifiers

Qualifiers allow you to differentiate between multiple definitions of the same type in your Koin modules.

When You Need Qualifiers

You need qualifiers when:

  • You have multiple implementations of the same interface
  • You need different configurations of the same type
  • You want to distinguish between instances with different purposes
// Without qualifiers - conflict!
val networkModule = module {
single { OkHttpClient.Builder()...build() }
single { OkHttpClient.Builder()...build() } // Which one?
}

Named Qualifiers

Use named() to distinguish between definitions:

Defining

import org.koin.core.qualifier.named

val networkModule = module {
single(named("encrypted")) {
OkHttpClient.Builder()
.addInterceptor(EncryptionInterceptor())
.build()
}

single(named("logging")) {
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor())
.build()
}
}

Injecting

// In module definitions
val apiModule = module {
single {
ApiService(
encryptedClient = get(named("encrypted")),
loggingClient = get(named("logging"))
)
}
}

// With KoinComponent
class MyService : KoinComponent {
private val encryptedClient: OkHttpClient by inject(named("encrypted"))
}

With Annotations

import org.koin.core.annotation.Named
import org.koin.core.annotation.Single

@Single
@Named("encrypted")
class EncryptedHttpClient : OkHttpClient()

@Single
@Named("logging")
class LoggingHttpClient : OkHttpClient()

@Single
class ApiService(
@Named("encrypted") private val encryptedClient: OkHttpClient,
@Named("logging") private val loggingClient: OkHttpClient
)
note

For Compiler Plugin DSL and Classic DSL autowire (singleOf, factoryOf), qualifiers cannot be automatically resolved. Use Classic DSL with lambdas or Annotations when definitions require qualifiers.

Type-Safe Qualifiers

Using Types

Use named<T>() with any type as a qualifier:

// Define qualifier types
object EncryptedClient
object LoggingClient

val networkModule = module {
single(named<EncryptedClient>()) {
OkHttpClient.Builder()
.addInterceptor(EncryptionInterceptor())
.build()
}

single(named<LoggingClient>()) {
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor())
.build()
}
}

// Inject
val client: OkHttpClient = get(named<EncryptedClient>())

Using Enums

For better IDE support, use enums:

enum class NetworkClient {
ENCRYPTED,
LOGGING,
FAST
}

val networkModule = module {
single(named(NetworkClient.ENCRYPTED)) {
OkHttpClient.Builder()
.addInterceptor(EncryptionInterceptor())
.build()
}

single(named(NetworkClient.LOGGING)) {
OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor())
.build()
}
}

// Inject - typo impossible!
val client: OkHttpClient = get(named(NetworkClient.ENCRYPTED))

Benefits of type-safe qualifiers:

  • Compile-time type safety
  • No string typos
  • IDE autocomplete and refactoring support

JSR-330 @Qualifier

Koin supports standard JSR-330 @Qualifier annotations:

import jakarta.inject.Qualifier

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

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

// Use in definitions
@Single
@IoDispatcher
fun provideIoDispatcher(): CoroutineDispatcher = Dispatchers.IO

// Use in injection
@Single
class MyRepository(
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
)

Common Use Cases

Multiple API Versions

val networkModule = module {
single(named("api_v1")) {
Retrofit.Builder()
.baseUrl("https://api.example.com/v1/")
.build()
}

single(named("api_v2")) {
Retrofit.Builder()
.baseUrl("https://api.example.com/v2/")
.build()
}
}

Different Timeout Configurations

val networkModule = module {
single(named("fast")) {
OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.build()
}

single(named("slow")) {
OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build()
}
}

Environment Configurations

val configModule = module {
single(named("debug")) {
AppConfig(apiUrl = "https://dev.example.com", loggingEnabled = true)
}

single(named("release")) {
AppConfig(apiUrl = "https://api.example.com", loggingEnabled = false)
}

// Select based on environment
single<AppConfig> {
if (isDebug) get(named("debug")) else get(named("release"))
}
}

Best Practices

1. Use Qualifiers Sparingly

// Good - qualifiers only when necessary
val appModule = module {
single { UserRepository(get()) } // No qualifier needed
single { AuthRepository(get()) } // Different types
}

// Over-qualification - avoid
val appModule = module {
single(named("user_repository")) { UserRepository(get()) }
single(named("auth_repository")) { AuthRepository(get()) }
// Unnecessary - types are already different
}

2. Prefer Type Differentiation

// Better - use different types
interface EncryptedHttpClient
interface LoggingHttpClient

class EncryptedOkHttpClient : OkHttpClient(), EncryptedHttpClient
class LoggingOkHttpClient : OkHttpClient(), LoggingHttpClient

val networkModule = module {
single<EncryptedHttpClient> { EncryptedOkHttpClient() }
single<LoggingHttpClient> { LoggingOkHttpClient() }
}

3. Avoid Qualifier Chains

// Bad - complex qualifier dependencies
val badModule = module {
single(named("a")) { A() }
single(named("b")) { B(get(named("a"))) }
single(named("c")) { C(get(named("b"))) }
}

// Better - flatten or use different types
val goodModule = module {
single { A() }
single { B(get()) }
single { C(get()) }
}

4. Document Qualifiers

val networkModule = module {
// Client for secure API calls with encryption
single(named("encrypted")) { ... }

// Client for debugging with full request/response logging
single(named("logging")) { ... }
}

Naming Conventions

String-Based

// Good - descriptive, lowercase with underscores
single(named("encrypted_client")) { ... }
single(named("user_database")) { ... }
single(named("api_v2")) { ... }

// Avoid - unclear or inconsistent
single(named("client1")) { ... } // What does "1" mean?

Enum-Based

// Good - clear enum names
enum class DatabaseType {
USER_DATA,
CACHE,
ANALYTICS
}

enum class ApiVersion {
V1, V2, V3
}

Common Pitfalls

Forgetting Qualifiers on Injection

val module = module {
single(named("encrypted")) { OkHttpClient() }
}

val repoModule = module {
single {
MyRepository(get()) // ❌ Error: No definition for OkHttpClient
// Should be: get(named("encrypted"))
}
}

Mismatched Qualifier Names

val module = module {
single(named("encrypted_client")) { OkHttpClient() }
}

val repoModule = module {
single {
ApiService(
get(named("encrypted")) // ❌ Typo! Should be "encrypted_client"
)
}
}

Use enum qualifiers to avoid typos!

Next Steps