Classes and Objects

This section documents Kotlin 2.4.x on the JVM, as published at kotlinlang.org, which is the reference these pages are written and verified against.

This content was generated with the assistance of AI and should be verified against kotlinlang.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Kotlin classes fold field declarations, a getter, a setter, and constructor parameters into one concise declaration wherever the intent is a plain data holder — with everything Java-style, fully explicit, still available when needed.

The Primary Constructor

Constructor parameters that also become properties are declared directly in the class header:

class Person(val name: String, var age: Int)

val ada = Person("Ada", 30)
println(ada.name)      // "Ada" -- name is a read-only property (val)
ada.age = 31            // age is mutable (var)

This one line replaces what Java would spell out as two fields, a constructor, and (if using getters/setters at all) two accessor methods. A primary-constructor parameter without val/var is just a plain constructor parameter, not a property, and is only usable inside an init block or another property initializer.

Secondary Constructors and init Blocks

A class can declare additional constructors with constructor, each of which must ultimately delegate to the primary constructor via this(…​). init blocks run, in declaration order interleaved with property initializers, as part of the primary constructor’s execution — the place to put validation or derived setup that a one-line parameter list cannot express:

class Rectangle(val width: Double, val height: Double) {

    val area: Double                      // property with no initializer here...

    init {
        require(width > 0 && height > 0) { "dimensions must be positive" }
        area = width * height              // ...assigned inside init
    }

    constructor(side: Double) : this(side, side)   // secondary constructor -- delegates to the primary
}

val square = Rectangle(4.0)      // uses the secondary constructor; area == 16.0

Properties: Custom Getters, Setters, and Backing Fields

Any property can replace its default accessor with a custom one. A backing field, referenced as field inside the accessor, is generated automatically whenever an accessor actually reads or writes the property’s stored value:

class Temperature(celsiusInitial: Double) {
    var celsius: Double = celsiusInitial
        set(value) {
            field = value                          // "field" is the auto-generated backing field
            println("celsius set to $value")
        }

    val fahrenheit: Double                          // a computed, read-only property -- no backing field at all
        get() = celsius * 9 / 5 + 32
}

val t = Temperature(20.0)
println(t.fahrenheit)     // 68.0 -- recomputed on every access
t.celsius = 25.0            // triggers the custom setter, prints "celsius set to 25.0"

A property with no custom accessor at all is exactly the field + getter (+ setter, for var) pair a Java record/POJO would hand-write.

Visibility Modifiers

Modifier Visible from

public (default)

everywhere — Kotlin’s default, unlike Java’s package-private default.

internal

anywhere in the same Gradle/Maven module — Kotlin’s addition, with no Java equivalent; the closest Java analogue, package-private, is module-agnostic and visible to any class in the same package regardless of which JAR it comes from.

protected

the declaring class and its subclasses (not the whole package, unlike Java’s protected).

private

the declaring class only (or, for a top-level declaration, the containing file only).

Classes Are final by Default

Unlike Java, where any non-final class can be subclassed, every Kotlin class is implicitly final — inheriting from it requires the base class to be explicitly marked open:

class Base                 // cannot be subclassed
open class OpenBase         // can be subclassed -- see Inheritance and Interfaces

// class Derived : Base()   // compile error: Base is final
class Derived : OpenBase()  // fine

This default exists to make inheritance a deliberate, opt-in design decision rather than an accident of omitting final — and it is why frameworks that need to subclass application classes behind the scenes (most notably Spring’s CGLIB-proxied @Configuration/@Service beans) require an "all-open" compiler plugin; see Inheritance and Interfaces and Java or Kotlin for Spring Boot? for how that plugin resolves it.

See Also