CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-insert-koin--koin-core-iossimulatorarm64

Koin Core is a pragmatic lightweight dependency injection framework for Kotlin Multiplatform with iOS Simulator ARM64 target support.

Pending
Overview
Eval results
Files

error-handling.mddocs/

Error Handling

This document covers the comprehensive exception system in Koin Core, helping you understand and handle dependency injection failures effectively.

Overview

Koin provides specific exception types for different failure scenarios, enabling precise error handling and debugging. The exception system covers:

  • Resolution failures - When dependencies cannot be found or created
  • Scope failures - When scope operations fail or scopes are misused
  • Configuration failures - When module or application setup goes wrong
  • Parameter failures - When parameter passing or resolution fails

Understanding these exceptions helps you build robust applications with proper error recovery and meaningful error messages.

Exception Hierarchy

All Koin exceptions extend from standard Kotlin/Java Exception class:

// Base class (standard Kotlin)
open class Exception : Throwable

// Koin-specific exceptions (all in org.koin.core.error package)
class NoDefinitionFoundException(msg: String) : Exception(msg)
class InstanceCreationException(msg: String, parent: Exception) : Exception(msg, parent)
class ClosedScopeException(msg: String) : Exception(msg)
// ... and others

Resolution Failures

NoDefinitionFoundException

Thrown when a requested dependency cannot be found in any loaded module.

class NoDefinitionFoundException(msg: String) : Exception(msg)

Common Causes

val emptyModule = module { 
    // No UserService definition
}

val koin = koinApplication { modules(emptyModule) }.koin

try {
    val service: UserService = koin.get()  // NoDefinitionFoundException!
} catch (e: NoDefinitionFoundException) {
    println("Service not found: ${e.message}")
    // Handle missing dependency
}

Prevention and Handling

// Prevention - ensure all dependencies are defined
val completeModule = module {
    single<UserService> { UserService(get()) }
    single<UserRepository> { UserRepository() }
}

// Handling - use nullable variants
val koin = koinApplication { modules(emptyModule) }.koin

val optionalService: UserService? = koin.getOrNull()
if (optionalService == null) {
    println("UserService not available, using fallback")
    // Use fallback implementation
}

Qualified Dependency Issues

val qualifiedModule = module {
    single<ApiService>(named("v1")) { ApiServiceV1() }
}

val koin = koinApplication { modules(qualifiedModule) }.koin

try {
    // Wrong qualifier - will throw NoDefinitionFoundException
    val service: ApiService = koin.get(named("v2"))
} catch (e: NoDefinitionFoundException) {
    // Fallback to v1 or handle error
    val fallbackService: ApiService = koin.get(named("v1"))
}

InstanceCreationException

Thrown when a definition exists but instance creation fails due to errors in the definition lambda.

class InstanceCreationException(msg: String, parent: Exception) : Exception(msg, parent)

Common Scenarios

val problematicModule = module {
    single<DatabaseService> {
        // This will throw during creation
        throw RuntimeException("Database connection failed")
    }
    
    factory<FileService> { params ->
        val path: String = params.get()
        if (!File(path).exists()) {
            throw IllegalArgumentException("File not found: $path")
        }
        FileService(path)
    }
}

val koin = koinApplication { modules(problematicModule) }.koin

try {
    val dbService: DatabaseService = koin.get()
} catch (e: InstanceCreationException) {
    println("Failed to create DatabaseService: ${e.message}")
    println("Root cause: ${e.cause}")
    // Use mock or fallback implementation
}

Handling Creation Failures

class RobustService : KoinComponent {
    private val primaryService: DataService? by lazy {
        try {
            get<DataService>(named("primary"))
        } catch (e: InstanceCreationException) {
            logger.warn("Primary service creation failed", e)
            null
        }
    }
    
    private val fallbackService: DataService by lazy {
        get<DataService>(named("fallback"))
    }
    
    fun processData(): Result {
        val service = primaryService ?: fallbackService
        return service.process()
    }
}

Scope Failures

ClosedScopeException

Thrown when attempting to use a scope that has been closed.

class ClosedScopeException(msg: String) : Exception(msg)

Typical Usage Scenarios

val scopedModule = module {
    scope<UserSession> {
        scoped<SessionData> { SessionData() }
    }
}

val koin = koinApplication { modules(scopedModule) }.koin
val scope = koin.createScope<UserSession>("session-123")

// Use scope normally
val sessionData: SessionData = scope.get()

// Close scope
scope.close()

try {
    // This will throw ClosedScopeException
    val data: SessionData = scope.get()
} catch (e: ClosedScopeException) {
    println("Attempted to use closed scope: ${e.message}")
    // Create new scope or handle error
    val newScope = koin.createScope<UserSession>("session-124")
}

Safe Scope Usage

class ScopeManager : KoinComponent {
    private var currentScope: Scope? = null
    
    fun getSessionData(): SessionData? {
        val scope = currentScope
        return if (scope != null && !scope.closed) {
            try {
                scope.get<SessionData>()
            } catch (e: ClosedScopeException) {
                currentScope = null
                null
            }
        } else {
            null
        }
    }
    
    fun createNewSession(): Scope {
        currentScope?.takeUnless { it.closed }?.close()
        currentScope = getKoin().createScope<UserSession>(generateSessionId())
        return currentScope!!
    }
}

ScopeNotCreatedException

Thrown when trying to access a scope that doesn't exist.

class ScopeNotCreatedException(msg: String) : Exception(msg)
val koin = koinApplication { modules(scopedModule) }.koin

try {
    // Scope with this ID doesn't exist
    val scope = koin.getScope("non-existent-scope")
} catch (e: ScopeNotCreatedException) {
    println("Scope not found: ${e.message}")
    // Create the scope or handle missing scope
    val newScope = koin.createScope<UserSession>("non-existent-scope")
}

// Safe alternative
val safeScope: Scope? = koin.getScopeOrNull("non-existent-scope")
if (safeScope == null) {
    println("Scope doesn't exist, creating new one")
    // Handle missing scope gracefully
}

ScopeAlreadyCreatedException

Thrown when attempting to create a scope with an ID that already exists.

class ScopeAlreadyCreatedException(msg: String) : Exception(msg)
val koin = koinApplication { modules(scopedModule) }.koin

// Create first scope
val scope1 = koin.createScope<UserSession>("duplicate-id")

try {
    // This will throw ScopeAlreadyCreatedException
    val scope2 = koin.createScope<UserSession>("duplicate-id")
} catch (e: ScopeAlreadyCreatedException) {
    println("Scope already exists: ${e.message}")
    // Use existing scope or generate new ID
    val existingScope = koin.getScope("duplicate-id")
}

// Safe alternative - get or create pattern
val safeScope = koin.getOrCreateScope<UserSession>("duplicate-id")

NoScopeDefFoundException

Thrown when trying to create a scope for which no scope definition exists.

class NoScopeDefFoundException(msg: String) : Exception(msg)
val moduleWithoutScope = module {
    single<Service> { Service() }
    // No scope definition for UndefinedScope
}

val koin = koinApplication { modules(moduleWithoutScope) }.koin

class UndefinedScope

try {
    // No scope definition exists for UndefinedScope
    val scope = koin.createScope<UndefinedScope>("test")
} catch (e: NoScopeDefFoundException) {
    println("No scope definition found: ${e.message}")
    // Define the scope or use different approach
}

MissingScopeValueException

Thrown when a scoped instance is expected but not found within a scope.

class MissingScopeValueException(msg: String) : Exception(msg)

Parameter Failures

NoParameterFoundException

Thrown when a definition expects parameters that are not provided.

class NoParameterFoundException(msg: String) : Exception(msg)
val parameterModule = module {
    factory<DatabaseConnection> { params ->
        val host: String = params.get()     // Expects String parameter
        val port: Int = params.get()        // Expects Int parameter
        DatabaseConnection(host, port)
    }
}

val koin = koinApplication { modules(parameterModule) }.koin

try {
    // No parameters provided - will throw NoParameterFoundException
    val connection: DatabaseConnection = koin.get()
} catch (e: NoParameterFoundException) {
    println("Missing parameters: ${e.message}")
    // Provide required parameters
    val connection: DatabaseConnection = koin.get {
        parametersOf("localhost", 5432)
    }
}

DefinitionParameterException

Thrown when there are parameter type mismatches or parameter access errors.

class DefinitionParameterException(msg: String) : Exception(msg)
val typedParameterModule = module {
    factory<Service> { params ->
        val config: ServiceConfig = params.get()  // Expects ServiceConfig
        Service(config)
    }
}

val koin = koinApplication { modules(typedParameterModule) }.koin

try {
    // Wrong parameter type - will throw DefinitionParameterException
    val service: Service = koin.get {
        parametersOf("wrong-type")  // String instead of ServiceConfig
    }
} catch (e: DefinitionParameterException) {
    println("Parameter type error: ${e.message}")
    // Provide correct parameter type
    val service: Service = koin.get {
        parametersOf(ServiceConfig("correct"))
    }
}

Configuration Failures

DefinitionOverrideException

Thrown when attempting to override a definition when overrides are not allowed.

class DefinitionOverrideException(msg: String) : Exception(msg)
val firstModule = module {
    single<Service> { ServiceImpl1() }
}

val secondModule = module {
    single<Service> { ServiceImpl2() }  // Same type, no qualifier
}

try {
    val koin = koinApplication {
        allowOverride(false)  // Overrides disabled
        modules(firstModule, secondModule)
    }.koin
} catch (e: DefinitionOverrideException) {
    println("Definition override not allowed: ${e.message}")
    
    // Solutions:
    // 1. Enable overrides
    val koinWithOverrides = koinApplication {
        allowOverride(true)
        modules(firstModule, secondModule)
    }.koin
    
    // 2. Use qualifiers
    val qualifiedModule = module {
        single<Service>(named("impl1")) { ServiceImpl1() }
        single<Service>(named("impl2")) { ServiceImpl2() }
    }
}

KoinApplicationAlreadyStartedException

Thrown when trying to start a Koin application that's already started (in global context scenarios).

class KoinApplicationAlreadyStartedException(msg: String) : Exception(msg)

MissingPropertyException

Thrown when a required property is not found.

class MissingPropertyException(msg: String) : Exception(msg)
val propertyModule = module {
    single<DatabaseConfig> {
        val host = getProperty<String>("db.host")  // Required property
        val port = getProperty<Int>("db.port")
        DatabaseConfig(host, port)
    }
}

val koin = koinApplication {
    modules(propertyModule)
    // Properties not set
}.koin

try {
    val config: DatabaseConfig = koin.get()
} catch (e: MissingPropertyException) {
    println("Missing property: ${e.message}")
}

// Provide properties
val koinWithProps = koinApplication {
    properties(mapOf(
        "db.host" to "localhost",
        "db.port" to 5432
    ))
    modules(propertyModule)
}.koin

NoPropertyFileFoundException

Thrown when a specified property file cannot be found.

class NoPropertyFileFoundException(msg: String) : Exception(msg)

Error Handling Strategies

1. Graceful Degradation

class ResilientService : KoinComponent {
    private val primaryCache: Cache? = try {
        get<Cache>(named("redis"))
    } catch (e: NoDefinitionFoundException) {
        logger.warn("Redis cache not available, using in-memory cache")
        null
    }
    
    private val fallbackCache: Cache by lazy { get<Cache>(named("memory")) }
    
    fun getCache(): Cache = primaryCache ?: fallbackCache
}

2. Retry Mechanisms

class RetryingComponent : KoinComponent {
    fun getServiceWithRetry(maxAttempts: Int = 3): Service {
        repeat(maxAttempts) { attempt ->
            try {
                return get<Service>()
            } catch (e: InstanceCreationException) {
                if (attempt == maxAttempts - 1) throw e
                logger.warn("Service creation failed (attempt ${attempt + 1}), retrying...")
                Thread.sleep(1000 * (attempt + 1))  // Exponential backoff
            }
        }
        throw IllegalStateException("Should not reach here")
    }
}

3. Circuit Breaker Pattern

class CircuitBreakerComponent : KoinComponent {
    private var failureCount = 0
    private var lastFailureTime = 0L
    private val circuitBreakerTimeout = 30_000L  // 30 seconds
    private val failureThreshold = 3
    
    fun getServiceSafely(): Service? {
        if (isCircuitOpen()) {
            logger.warn("Circuit breaker open, service unavailable")
            return null
        }
        
        return try {
            val service: Service = get()
            resetCircuitBreaker()
            service
        } catch (e: Exception) {
            recordFailure()
            logger.error("Service access failed", e)
            null
        }
    }
    
    private fun isCircuitOpen(): Boolean {
        return failureCount >= failureThreshold && 
               (System.currentTimeMillis() - lastFailureTime) < circuitBreakerTimeout
    }
    
    private fun recordFailure() {
        failureCount++
        lastFailureTime = System.currentTimeMillis()
    }
    
    private fun resetCircuitBreaker() {
        failureCount = 0
        lastFailureTime = 0L
    }
}

4. Error Context Collection

class DiagnosticHelper : KoinComponent {
    fun diagnoseInjectionFailure(type: KClass<*>, qualifier: Qualifier? = null): String {
        val koin = getKoin()
        
        return buildString {
            appendLine("Dependency Injection Diagnostic Report")
            appendLine("Type: ${type.qualifiedName}")
            appendLine("Qualifier: ${qualifier?.value ?: "None"}")
            appendLine()
            
            // Check if any modules are loaded
            val moduleCount = koin.instanceRegistry.size()
            appendLine("Loaded definitions: $moduleCount")
            
            if (moduleCount == 0) {
                appendLine("❌ No modules loaded! Check your application setup.")
                return@buildString
            }
            
            // Check for similar types
            appendLine("Available similar types:")
            // Implementation would scan registry for similar types
            
            // Check for scope issues
            if (qualifier != null) {
                appendLine("Checking qualified definitions...")
                // Implementation would check for qualifier matches
            }
        }
    }
}

Best Practices

1. Use Nullable Variants for Optional Dependencies

// Good - graceful handling of optional dependencies
val optionalService: OptionalService? = koin.getOrNull()

// Avoid - throwing exceptions for optional features
try {
    val service: OptionalService = koin.get()
} catch (e: NoDefinitionFoundException) {
    // Optional service not available
}

2. Validate Critical Dependencies Early

class ApplicationStartup : KoinComponent {
    fun validateCriticalDependencies() {
        val criticalServices = listOf(
            DatabaseService::class,
            ConfigService::class,
            SecurityService::class
        )
        
        criticalServices.forEach { serviceClass ->
            try {
                getKoin().get(serviceClass, null, null)
                logger.info("✅ ${serviceClass.simpleName} available")
            } catch (e: NoDefinitionFoundException) {
                logger.error("❌ Critical service ${serviceClass.simpleName} missing")
                throw IllegalStateException("Application cannot start without ${serviceClass.simpleName}", e)
            }
        }
    }
}

3. Scope Lifecycle Management

class SafeScopeComponent : KoinScopeComponent {
    override val scope: Scope by lazy { createScope() }
    
    fun cleanup() {
        try {
            if (!scope.closed) {
                scope.close()
            }
        } catch (e: ClosedScopeException) {
            // Scope already closed, ignore
            logger.debug("Scope already closed during cleanup")
        }
    }
}

4. Comprehensive Error Logging

class ErrorLoggingComponent : KoinComponent {
    fun safeGet<T : Any>(
        type: KClass<T>,
        qualifier: Qualifier? = null,
        parameters: ParametersDefinition? = null
    ): T? {
        return try {
            getKoin().get(type, qualifier, parameters)
        } catch (e: NoDefinitionFoundException) {
            logger.warn("Definition not found for ${type.simpleName} with qualifier $qualifier")
            null
        } catch (e: InstanceCreationException) {
            logger.error("Failed to create instance of ${type.simpleName}", e)
            null
        } catch (e: Exception) {
            logger.error("Unexpected error resolving ${type.simpleName}", e)
            null
        }
    }
}

Understanding and properly handling Koin exceptions enables you to build robust applications that gracefully handle dependency injection failures and provide meaningful feedback for debugging and monitoring.

Install with Tessl CLI

npx tessl i tessl/maven-io-insert-koin--koin-core-iossimulatorarm64

docs

application-setup.md

components.md

dependency-injection.md

error-handling.md

index.md

modules-and-definitions.md

qualifiers-parameters.md

scoping.md

tile.json