Declaring one class inside another groups their names, but it does not automatically connect their instances. A normal nested class can be created through Outer.Nested without an Outer object. An inner class carries a reference to an outer instance and can use that instance's state.
That single keyword changes construction and lifetime. This guide builds both forms, explains qualified this, and shows why an inner object retained elsewhere can also retain its owner. Review the classes lesson first. The examples run independently on Kotlin/JVM and use only simple state so their output is predictable.
Nesting organizes a type name
A nested class is declared inside another class without inner. Its qualified name includes the outer type, which can communicate ownership of a helper concept. For example, Report.Options can describe options specific to reporting without requiring every options object to hold a report instance.
The nested class does not automatically see an outer object's properties. There is no particular outer instance attached to it. If it needs data, accept that data through parameters or constructor properties. This often produces a clearer dependency than silently capturing a large owner.
Use nesting when the type name belongs naturally to one component and hiding it at top level would not improve reuse. Do not nest several levels merely to simulate folders. A deeply qualified type name can become hard to read, and a reusable value type may deserve its own top-level declaration.
Inner classes attach an outer instance
Mark a nested class inner when its behavior genuinely needs the enclosing object's live state. Construct it through an existing outer instance, such as owner.Inner(). The inner object can then read accessible outer members because it retains a relationship with that owner.
If the outer state changes, the inner operation can observe the new value. That differs from passing a snapshot into an ordinary nested class. Decide whether the helper should represent current owner state or a stable value captured at creation. The choice affects both output and testing.
An inner class is not simply a more powerful default. Its outer reference adds coupling and can extend the owner's lifetime. Prefer an ordinary nested or top-level class with explicit small inputs when that fulfills the task. Use inner when the live relationship is part of the intended model.
Resolve this deliberately
Inside an inner class, this refers to the inner object. Use this@OuterName to refer explicitly to the outer instance when names overlap or when passing the owner to another operation. Qualified this is also useful in receiver-based lambdas, but each label belongs to its actual context.
Avoid several overlapping names such as value on every nested level. Qualification can resolve them technically while the code remains difficult to understand. Names like ownerTitle and entryTitle can communicate the relationship more directly. Use qualification when it adds precision, not to compensate for unnecessary nesting.
Visibility still applies. A nested type's public status does not automatically expose every private detail of its instances to external callers. Likewise, inner grants an outer relationship, not a public permission policy. Keep fields private and expose the operations callers actually need.
Lifetime and platform considerations
An inner object holds its outer object alive while that relationship remains reachable. In a UI application, storing an inner callback or helper in a long-lived registry can therefore retain a screen or controller unexpectedly. The same issue can arise with closures capturing an owner, so inspect the complete ownership graph.
Kotlin/JVM object lifetime is governed by garbage collection rather than deterministic destruction. A retained outer reference does not necessarily create an unrecoverable leak, but it can prevent collection as long as a long-lived root holds the inner object. Release registrations and choose explicit small dependencies where appropriate.
The official nested and inner class guide explains construction and outer references. Object expressions are a separate way to create anonymous implementations and are already introduced in the objects lesson. Do not add inner when a stateless object expression or explicit callback is sufficient.
Kotlin Nested and Inner Classes Examples
Each example shows the code, the output it produces and what each part does.
Construct a nested options value
No Report instance is created. Options is a nested data class whose value is supplied explicitly. Nesting contributes a meaningful qualified name, while data-class behavior contributes value equality and a readable representation. Those are separate language choices rather than one automatic effect of placing a class inside another.
class Report {
data class Options(val includeTotals: Boolean)
}
fun main() {
val options = Report.Options(true)
println(options.includeTotals)
}trueRead a live outer property
Reader does not store an independent snapshot of value. It reads its associated Counter. If a caller needs the original two permanently, pass that value into a separate snapshot type instead. The example makes the distinction visible by updating the owner between two reads.
class Counter(var value: Int) {
inner class Reader {
fun current(): Int = value
}
}
fun main() {
val counter = Counter(2)
val reader = counter.Reader()
println(reader.current())
counter.value = 5
println(reader.current())
}2
5Kotlin Nested and Inner Classes in Real Projects
Worked exercise: a current catalog label
Create a catalog with a mutable display title and an inner label helper that combines the current title with a supplied item. The output must follow a later title change. This is an intentional live view, so inner is appropriate. The helper should not copy the title into its own property.
class Catalog(var title: String) {
inner class Label {
fun text(item: String): String = "${this@Catalog.title}: $item"
}
}
fun main() {
val catalog = Catalog("Office")
val label = catalog.Label()
println(label.text("Pen"))
catalog.title = "School"
println(label.text("Pen"))
}Office: Pen
School: PenThe qualified owner expression makes the source of title explicit. For a printed historical receipt, a live label would be wrong because later catalog changes should not rewrite past wording. In that case, construct a separate snapshot value. Design the lifetime from the business requirement rather than choosing inner merely because it avoids one constructor parameter.
Kotlin Nested and Inner Classes FAQ
Does a nested class need an outer object?
An ordinary nested class does not. Its name is grouped under the outer type, but it has no automatic relationship to an outer instance. You construct it with the outer type name and supply required data explicitly. An inner class is different and is constructed through an existing owner. Keeping those forms distinct helps you choose between a stable value and a helper that intentionally reads the owner's current state throughout its lifetime.
Can an inner class observe later outer changes?
Yes when it reads the relevant outer property during its operation. It retains the owner rather than automatically copying all fields at creation. That can be useful for a live view but wrong for a historical snapshot. State the required lifetime clearly and choose explicit copied values when later changes must not affect the result. Inner also does not synchronize concurrent changes, so a live relationship is not a thread-safety guarantee.
Why might an inner helper keep a screen alive?
The helper carries a reference to its outer instance. If a global registry, listener list or long-lived service retains that helper, it can also retain the screen owning it. Release registrations when their lifecycle ends, or use a helper with only the small dependencies it needs. A weak reference is not automatically the right remedy; first clarify who should own the operation and whether the owner relationship is necessary at all.
When should I keep a helper at top level instead?
Use a top-level class when it represents a reusable concept, when several owners need it or when the outer name does not add useful context. A nested type fits a concept closely associated with one component, and inner fits a deliberate live owner relationship. These choices organize responsibilities; none is a quality score. Favor the structure that makes construction, dependencies and lifetime easiest for a caller to understand without tracing several enclosing declarations.
Kotlin Nested and Inner Classes Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
A receipt label must never change after purchase. Which model best expresses that requirement?
Kotlin Nested and Inner Classes Quiz
2 questions to check what you learned.
Which construction normally creates a plain nested class?
- Outer.Nested()
- Outer().inner without a class name
- new inner Outer automatically
What does inner add to a nested class?
- Automatic immutable snapshots
- A relationship to an outer object instance
- Database persistence