Using Kotlin Delegated Properties to Offload Getter/Setter Logic
Learn how Kotlin delegated properties offload getter/setter logic to a delegate object, with a worked lazy/observable example, limits, and common pitfalls.
15 Dec 2025, 02:51 UTC

What delegated properties solve
Sometimes a property’s getter or setter contains reusable logic such as lazy initialization, change observation, or thread‑safety. Instead of repeating that code in every class, Kotlin lets you delegate the property to another object that implements the required getValue (and setValue for var) functions.
How delegation works
When you write prop: Type by delegate, the compiler stores the delegate object as a hidden field and replaces property accesses with calls to delegate.getValue(this, ::prop) (or setValue for assignments). The delegate must provide the matching signatures; the property itself holds no backing field.
Worked example: lazy and observable delegates
import kotlin.properties.Delegates
import org.slf4j.LoggerFactory
class Service {
// Lazy‑initialized logger – thread‑safe by default
val logger: Logger by lazy { LoggerFactory.getLogger(Service::class.java) }
// Observable name – prints every change
var name: String by Delegates.observable("") { _, old, new ->
println("Name changed from '$old' to '$new'")
}
}
fun main() {
val s = Service()
// First access triggers lazy lambda
_ = s.logger
// Assignment triggers observable lambda
s.name = "Alice"
s.name = "Bob"
}
The lazy delegate creates the logger on first access; subsequent reads reuse the stored instance. The observable delegate stores the current value and invokes the lambda whenever the property is assigned.
Limits and runtime overhead
- Delegates must implement
getValue(andsetValuefor mutable properties); otherwise the code does not compile. - Because the delegate is an extra object, each property access incurs an additional method call and field read, which is usually negligible but measurable in tight loops.
- A delegated property cannot be marked
constorlateinit; the delegate supplies the value at runtime.
Common mistakes and how to avoid them
- Forgotten delegate initialization – using a
lazydelegate that throwsIllegalStateExceptionif the lambda captures a variable that is later changed. Ensure the lambda captures only immutable values or useLazyThreadSafetyModeexplicitly. - Mutating the delegate instance – if you replace the delegate object after the property has been accessed, reads may see inconsistent state. Treat the delegate as immutable after construction.
- Using a non‑thread‑safe delegate concurrently – the built‑in
lazyis thread‑safe, but custom delegates may not be. Either synchronize access or choose a thread‑safe alternative such asDelegates.observable(which is not synchronized, so add your own locking if needed).
Checking that delegation works
- Compile the file with
kotlinc -jvm-target 1.8 Example.kt(requires Kotlin 1.9+). - Run the produced
ExampleKtclass and observe the lazy initialization and observable messages. - Use a debugger to step into the delegate’s
getValueandsetValuemethods and confirm they are invoked on property access and assignment. - Inspect the generated bytecode with
javap -c -p ExampleKt.class; you should see a field for the delegate and calls to its methods.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.