Getting Started

Add Kontainer to your project and wire up your first services.

Prerequisites

  • JDK 17+
  • Gradle with Kotlin JVM or multiplatform plugin

1. Add the dependency

In your build.gradle.kts:

dependencies {
    implementation("io.peekandpoke.ultra:kontainer:0.107.2")
}

Find the latest version on Maven Central.

2. Define a blueprint

A blueprint is an immutable description of all your services and their lifecycles. You create it once, then use it to stamp out container instances.

import io.peekandpoke.ultra.kontainer.kontainer

// Define some services
class Greeter {
    fun sayHello() = "Hello!"
}

// Create a blueprint
val blueprint = kontainer {
    singleton(Greeter::class)
}

3. Create a container and use it

// Create a container from the blueprint
val kontainer = blueprint.create()

// Retrieve a service
val greeter = kontainer.get(Greeter::class)
println(greeter.sayHello())  // "Hello!"

4. Add dependencies between services

Services declare their dependencies in the constructor. Kontainer resolves them automatically.

class Counter {
    private var count = 0
    fun next() = ++count
}

class MyService(val counter: Counter)

val blueprint = kontainer {
    singleton(Counter::class)
    singleton(MyService::class)
}

val kontainer = blueprint.create()
val service = kontainer.get(MyService::class)

println(service.counter.next())  // 1
println(service.counter.next())  // 2

Kontainer inspects the primary constructor of MyService, sees it needs a Counter, and injects it. No annotations needed.

5. Use factory methods when constructors aren't enough

Sometimes a service needs parameters the container doesn't know about. Use a factory lambda:

class MyService(private val counter: Counter, private val offset: Int) {
    fun next() = counter.next() + offset
}

val blueprint = kontainer {
    singleton(Counter::class)

    // Factory: Kontainer injects Counter, you provide the offset
    singleton(MyService::class) { counter: Counter ->
        MyService(counter, 100)
    }
}

What happens on first use?

The first time you call blueprint.create(), Kontainer validates the entire dependency graph:

  • Are all required dependencies registered?
  • Are there ambiguous services (multiple implementations for one type)?
  • Can all constructors be satisfied?

If anything is wrong, you get a KontainerInconsistent exception with a clear message listing every problem — not a cryptic runtime error later.