Ktor 3.4.1 Help

Dependency resolution

After you register dependencies, you can resolve them from the dependency injection (DI) container and inject them into application code.

You can resolve dependencies explicitly from the DI container using either property delegation or direct resolution.

Use property delegation

When using property delegation, the dependency is resolved lazily when the property is first accessed:

val service: GreetingService by dependencies

Use direct resolution

Direct resolution returns the dependency immediately or suspends until it becomes available:

val service = dependencies.resolve<GreetingService>()

Parameter resolution

When resolving constructors or functions, Ktor resolves parameters using the DI container. Parameters are resolved by type by default.

If type-based resolution is insufficient, you can use annotations to explicitly bind parameters.

Use named dependencies

Use the @Named annotation to resolve a dependency registered with a specified name:

fun Application.userRepository(@Named("mongo") database: Database) { // Uses the dependency named "mongo" }

Use configuration properties

Use the @Property annotation to inject a value from the application configuration:

fun provideDatabase( @Property("database.connectionUrl") connectionUrl: String ): Database = PostgresDatabase(connectionUrl)

In the above example, the database.connectionUrl property is resolved from the application configuration:

database: connectionUrl: postgres://localhost:5432/admin

Asynchronous dependency resolution

To support asynchronous loading, you can use suspending functions:

data class EventsConnection(val connected: Boolean) suspend fun Application.installEvents() { val conn: EventsConnection = dependencies.resolve() log.info("Events connection ready: $conn") } suspend fun Application.loadEventsConnection() { dependencies.provide { delay(200) // simulate async work EventsConnection(true) } }

The DI plugin will automatically suspend resolve() calls until all dependencies are ready.

Inject dependencies into application modules

You can inject dependencies directly into application modules by specifying parameters in the module function. Ktor will resolve these dependencies from the DI container based on type matching.

First, register your dependency providers in the ktor.application.dependencies group in your configuration file:

ktor: application: dependencies: - com.example.PrintStreamProviderKt.stdout modules: - com.example.LoggingKt.logging

Define the dependency provider and module function with parameters for the dependencies you want injected. You can then use the injected dependencies directly within the module function:

package com.example import java.io.PrintStream fun stdout(): () -> PrintStream = { System.out }
package com.example import io.ktor.server.application.* import io.ktor.server.plugins.di.dependencies import java.io.PrintStream class Logger(private val out: PrintStream) { fun log(message: String) { out.println("[LOG] $message") } } fun Application.logging(printStreamProvider: () -> PrintStream) { dependencies { provide<Logger> { Logger(printStreamProvider()) } } }

Advanced dependency resolution

Optional and nullable dependencies

Use nullable types to handle optional dependencies gracefully:

// Uses property delegation val config: Config? by dependencies // Uses direct resolution val config = dependencies.resolve<Config?>()

Covariant generics

Ktor's DI system supports type covariance, which allows injecting a value as one of its supertypes when the type parameter is covariant. This is especially useful for collections and interfaces that work with subtypes.

dependencies { provide<List<String>> { listOf("one", "two") } } // This will work due to type parameter covariance support val stringList: List<CharSequence> by dependencies // This will also work val stringCollection: Collection<CharSequence> by dependencies

Covariance also works with non-generic supertypes:

dependencies { provide<BufferedOutputStream> { BufferedOutputStream(System.out) } } // This works because BufferedOutputStream is a subtype of OutputStream val outputStream: OutputStream by dependencies

Limitations

While the DI system supports covariance for generic types, it currently does not support resolving parameterized types across type argument subtypes. That means you cannot retrieve a dependency using a type that is more specific or more general than what was registered.

For example, the following code will not resolve:

dependencies { provide<Sink<CharSequence>> { CsqSink() } } // Will not resolve val charSequenceSink: Sink<String> by dependencies
27 February 2026