Skip to main content
Version: 4.2

Starting Koin

This page covers how to start the Koin container, the options you can pass at start, typed startup with @KoinApplication, logging, properties, and how to load or unload modules later. It assumes you know what a module is (see Modules).

Code samples come in three tabs: Compiler Plugin DSL, Annotations, Constructor / Classic DSL. Pick yours, the choice applies to every page.

Start Koin​

import org.koin.core.context.startKoin

fun main() {
startKoin {
printLogger()
modules(appModule)
}
}

startKoin creates the container and registers it in GlobalContext, the default context for your process. Once it returns, every get(), by inject() or platform API (by viewModel(), koinInject()) resolves from this container.

You can call startKoin only once per process. A second call throws KoinApplicationAlreadyStartedException. To add modules after start, use loadKoinModules.

Start options​

Everything inside the startKoin { } block configures the container:

startKoin {
printLogger(Level.DEBUG)
properties(mapOf("server_url" to "https://api.example.com"))
allowOverride(false)
modules(coreModule, networkModule)
lazyModules(analyticsModule)
}
OptionWhat it does
modules(...)Loads modules immediately. Takes one module, several, or a List<Module>
lazyModules(...)Loads modules in the background. Needs koin-core-coroutines, see Lazy Modules
printLogger(level)Logs to the console. See Logging
logger(logger)Uses your own Logger implementation
properties(map)Adds properties from a map. See Properties
fileProperties(file)Loads properties from a classpath file, /koin.properties by default (JVM only)
environmentProperties()Loads system properties and environment variables (JVM only)
allowOverride(false)Fails when two definitions declare the same type without .override(). See Overriding a definition
strictOverride()Same as allowOverride(false)
createEagerInstances()Creates the createdAtStart definitions now. startKoin and koinApplication already do it at the end of the block, so you need it only with koinApplication(createEagerInstances = false)

The startKoin block runs on the calling thread. Every module you load there, and every createdAtStart definition, adds to startup time.

Typed startup with @KoinApplication​

With the Koin Compiler Plugin, an application class annotated with @KoinApplication describes which modules to load. You start Koin from that class with a typed call:

@Module
@ComponentScan("com.myapp.data")
class DataModule

@Module(includes = [DataModule::class])
class AppModule

@KoinApplication(modules = [AppModule::class])
class MyApp
CallResult
startKoin<MyApp>()Starts Koin in GlobalContext with the modules of MyApp
startKoin<MyApp> { ... }Same, with a configuration block (logger, properties, extra DSL modules)
koinApplication<MyApp>()Creates an isolated KoinApplication, not registered globally
koinConfiguration<MyApp>()Creates a KoinConfiguration, for APIs that start Koin for you (Compose KoinApplication, Ktor)
withConfiguration<MyApp>()Adds the modules of MyApp to an existing KoinApplication, for example inside Ktor's install(Koin) { }

These functions are imported from org.koin.plugin.module.dsl. They are compiler stubs: without the Compiler Plugin applied, they throw at runtime with a message telling you the plugin is missing.

@KoinApplication takes two parameters:

  • modules: the @Module classes to load explicitly.
  • configurations: the configuration labels to discover. See Configuration labels.

Each @Module class brings its includes and its @ComponentScan definitions. The configuration block of startKoin<MyApp> { } is a normal startKoin block, so you can mix annotated modules with DSL modules:

startKoin<MyApp> {
printLogger()
modules(legacyDslModule)
}

Loading @Module classes without @KoinApplication​

Inside a plain startKoin { } or koinApplication { } block, you can load @Module classes directly:

import org.koin.plugin.module.dsl.module
import org.koin.plugin.module.dsl.modules

startKoin {
module<NetworkModule>()
modules(DataModule::class, CacheModule::class)
}

This helps in tests, when you want only some modules. module<T>() and modules(vararg KClass) are compiler stubs too, and they don't work inside startKoin<T> { }, which the plugin already rewrites.

note

With the Compiler Plugin, there is no AppModule().module extension to call. Load annotated modules with @KoinApplication and startKoin<T>(), or with module<T>() as shown above.

Configuration labels​

A @Module tagged with @Configuration is discovered automatically by @KoinApplication, including from other Gradle modules on the classpath (see Auto-discovery with @Configuration). Labels let you pick which of these modules to load:

@Module
@Configuration
class CoreModule

@Module
@Configuration("prod")
class ProdDatabaseModule

@Module
@Configuration("test")
class TestDatabaseModule

@Module
@Configuration("default", "test")
class LoggingModule
@KoinApplication
class MyApp // loads CoreModule and LoggingModule

@KoinApplication(configurations = ["default", "prod"])
class ProdApp // loads CoreModule, LoggingModule and ProdDatabaseModule

@KoinApplication(configurations = ["test"])
class TestApp // loads TestDatabaseModule and LoggingModule

The rules:

  • @Configuration with no value is the "default" label. @Configuration("default") is the same.
  • @KoinApplication with no configurations loads the "default" label only.
  • When you list labels, only those labels load. Add "default" to the list if you still want the default modules.
  • A module can carry several labels.

Isolated instance with koinApplication​

koinApplication { } creates a container that is not registered in GlobalContext:

val koinApp = koinApplication {
modules(sdkModule)
}

val service = koinApp.koin.get<SdkService>()

Global APIs (KoinComponent, by inject() in an Activity) don't see this container. Use it for libraries and SDKs that must not share the host app's container, or for tests that need their own instance. The full pattern is in Context Isolation.

koinConfiguration { } builds a configuration without starting anything. You pass it to APIs that start Koin themselves, such as Compose KoinApplication and the Ktor plugin.

Starting on each platform​

  • Android: start Koin in Application.onCreate() with androidContext() and androidLogger(). See Starting Koin on Android.
  • Compose: start Koin from a composable with KoinApplication or KoinMultiplatformApplication. See Koin for Compose.
  • Ktor: install the Koin plugin in your Application module. See Koin for Ktor.
  • Kotlin Multiplatform: share an initKoin() function in commonMain and call it from each platform. See KMP Setup.

Load and unload modules at runtime​

After start, you can add and remove modules in the global container:

import org.koin.core.context.loadKoinModules
import org.koin.core.context.unloadKoinModules

if (premiumEnabled) {
loadKoinModules(premiumModule)
}

// later
unloadKoinModules(premiumModule)

Both functions take one module or a List<Module>. unloadKoinModules removes the definitions of the module and drops their instances, so a later get() for these types fails.

The Compiler Plugin can't verify a graph assembled at runtime: it can't know when, or if, the module is loaded. Types that other definitions need from a runtime-loaded module must be marked @Provided, and the plugin reports the module itself as not loaded at start. See Compile-Time Safety. If you load modules later only to speed up startup, Lazy Modules is usually a better fit.

Stop Koin​

import org.koin.core.context.stopKoin

stopKoin()

stopKoin() closes the global container: it releases every instance, calls the onClose callbacks of your definitions, and clears GlobalContext. You can then call startKoin again. For an isolated container, call close() on the KoinApplication:

koinApp.close()

In tests, stop Koin after each test so the next one starts clean. Testing shows the JUnit rules that do it for you.

Logging​

Koin logs nothing by default (EmptyLogger). Turn logging on in the start block:

startKoin {
printLogger(Level.DEBUG)
modules(appModule)
}

Level is DEBUG, INFO, WARNING, ERROR or NONE. The default level of each logger is INFO.

LoggerStart optionPlatformArtifact
EmptyLogger(default)Allkoin-core
PrintLoggerprintLogger()Allkoin-core
AndroidLoggerandroidLogger()Android (Logcat)koin-android
SLF4JLoggerslf4jLogger()JVMkoin-logger-slf4j

At DEBUG level, Koin logs each module load and each resolution with its duration. This is useful to find a slow definition, and too verbose for production.

To send logs somewhere else, extend Logger and pass it with logger():

import org.koin.core.logger.Level
import org.koin.core.logger.Logger
import org.koin.core.logger.MESSAGE

class AppLogger : Logger(Level.INFO) {
override fun display(level: Level, msg: MESSAGE) {
MyLogSystem.log(level.name, msg)
}
}

startKoin {
logger(AppLogger())
}

Properties​

Properties are key-value pairs stored in the container. Use them for configuration values such as URLs or keys.

Loading properties​

startKoin {
properties(mapOf("server_url" to "https://api.example.com"))
fileProperties() // JVM: reads /koin.properties from the classpath
environmentProperties() // JVM: system properties and environment variables
}

On Android, androidFileProperties() reads koin.properties from the assets folder. You can also set and read properties at runtime on the Koin instance:

val koin = KoinPlatform.getKoin()
koin.setProperty("session_token", token)
val token: String? = koin.getProperty("session_token")

Values loaded from a file or the environment are String. Convert them yourself (getProperty<String>("timeout").toInt()).

Injecting properties​

Annotate the constructor parameter with @Property (from koin-annotations):

class ApiClient(@Property("server_url") val url: String)

val networkModule = module {
single<ApiClient>()
}

Read properties in one configuration class and inject that class, instead of calling getProperty in many places. Keys then live in one file.

Limits​

  • startKoin runs once per process. Call stopKoin() before starting again.
  • fileProperties() and environmentProperties() exist on the JVM only. On other platforms, pass a map with properties().
  • Typed startup (startKoin<T>(), module<T>()) needs the Compiler Plugin. The KSP processor is deprecated, see Migrating from KSP.
  • Modules loaded with loadKoinModules after start are not part of the compile-time graph check.