docs/reference/koin-ktor/ktor-isolated.md
The KoinIsolated plugin runs Koin in an isolated context, separate from the global Koin instance. This is useful for testing, multi-tenant applications, and running multiple Koin instances.
Install KoinIsolated instead of Koin:
fun Application.main() {
install(KoinIsolated) {
slf4jLogger()
modules(appModule)
}
}
// Uses GlobalContext - shared across the application
install(Koin) {
modules(appModule)
}
// Can access Koin anywhere via GlobalContext
val service = GlobalContext.get().get<UserService>()
// Uses isolated context - not accessible via GlobalContext
install(KoinIsolated) {
modules(appModule)
}
// GlobalContext.get() will NOT return this Koin instance
// Only accessible within the Ktor application scope
:::warning
When using KoinIsolated, you cannot access Koin via GlobalContext. All injection must happen within the Ktor application scope using inject() or get().
:::
val appModule = module {
singleOf(::UserRepository)
singleOf(::UserService)
requestScope {
scopedOf(::RequestLogger)
}
}
fun Application.main() {
// Install Koin in isolated mode
install(KoinIsolated) {
slf4jLogger()
modules(appModule)
}
// Injection works within Application scope
val userService by inject<UserService>()
routing {
get("/users/{id}") {
val logger = call.scope.get<RequestLogger>()
val id = call.parameters["id"]!!
logger.log("Fetching user $id")
val user = userService.getUser(id)
call.respond(user)
}
}
}
The isolated context also supports the Ktor DI Bridge:
fun Application.main() {
// Ktor DI - Infrastructure
val database = Database(environment.config)
dependencies {
provide<Database> { database }
}
// Koin Isolated with bridge
install(KoinIsolated) {
slf4jLogger()
bridge {
koinToKtor() // Allow Koin to resolve from Ktor DI
}
modules(appModule)
}
routing {
userRoutes()
}
}
val appModule = module {
// Can inject Database from Ktor DI via bridge
singleOf(::UserRepository)
singleOf(::UserService)
}
Isolated context is particularly useful for testing:
class UserServiceTest {
@Test
fun `test user endpoint`() = testApplication {
application {
// Each test gets its own isolated Koin instance
install(KoinIsolated) {
modules(testModule)
}
configureRouting()
}
client.get("/users/123").apply {
assertEquals(HttpStatusCode.OK, status)
}
}
}
val testModule = module {
single<UserRepository> { MockUserRepository() }
singleOf(::UserService)
}
With isolated context, tests can run in parallel without interference:
class ParallelTests {
@Test
fun `test A`() = testApplication {
application {
install(KoinIsolated) {
modules(moduleA) // Own isolated instance
}
}
// ...
}
@Test
fun `test B`() = testApplication {
application {
install(KoinIsolated) {
modules(moduleB) // Different isolated instance
}
}
// ...
}
}
Run multiple Ktor servers with independent Koin instances:
fun main() {
// Server 1 - User Service
val userServer = embeddedServer(Netty, port = 8080) {
install(KoinIsolated) {
modules(userServiceModule)
}
userRouting()
}
// Server 2 - Order Service (different Koin instance)
val orderServer = embeddedServer(Netty, port = 8081) {
install(KoinIsolated) {
modules(orderServiceModule)
}
orderRouting()
}
// Both servers have independent Koin containers
userServer.start(wait = false)
orderServer.start(wait = true)
}
The isolated Koin instance follows the Ktor application lifecycle:
fun Application.main() {
install(KoinIsolated) {
slf4jLogger()
modules(appModule)
}
// Monitor Koin lifecycle
environment.monitor.subscribe(KoinApplicationStarted) {
log.info("Isolated Koin started")
}
environment.monitor.subscribe(KoinApplicationStopped) {
log.info("Isolated Koin stopped")
}
}
Within the Ktor application, you can access the isolated Koin instance:
fun Application.main() {
install(KoinIsolated) {
modules(appModule)
}
// Access Koin instance from Application
val koin = getKoin()
// Or via plugin attribute
val koinApp = attributes[KoinPluginKey]
}