Kotlin Properties and Fields in Classes

In Kotlin, class properties can be declared as mutable using var or read-only using val.

class Address {
    var name: String = "Holmes, Sherlock"
    var street: String = "Baker"
    var city: String = "London"
    var state: String? = null
    var zip: String = "123456"
}

fun duplicateAddress(source: Address): Address {
    val copy = Address()
    copy.name = source.name
    copy.street = source.street
    // ...
    return copy
}

Getters and Setters

The full syntax for declaring a property is:

var <name>[: <Type>] [= <initializer>]
    [<getter>]
    [<setter>]

The initializer, getter, and setter are optional. The type can often be inferred from the initializer or the getter’s return type.

// var allByDefault: Int? // ❌ Compilation error: must be initialized
var inferred = 1 // ✅ Type inferred as Int with default accessors

Read-only properties (val) cannot have setters:

val needsInit: Int // Must be initialized in constructor
val inferredVal = 1 // Type inferred as Int

Custom accessors allow computed or side-effecting behavior:

val isEmpty: Boolean get() = size == 0

var stringRepresentation: String
    get() = toString()
    set(value) {
        parseAndAssign(value)
    }

Since Kotlin 1.1, the property type can be omitted if it can be inferred from the getter:

val isEmpty get() = size == 0 // Type is Boolean

Visibility or annotations can be applied to accessors without changing their implementation:

var internalSetter: String = "default"
    private set

var injectedValue: Any? = null
    @Inject set

Backing Fields

Kotlin automatical provides a backing field when needed, accessible via the field identifier inside accessors:

var counter = 0
    set(value) {
        if (value >= 0) field = value
    }

The field keyword is only valid within property accessors.

Backing Properties

When automatic backing fields aren’t sufficient, a backing property can be used:

private var _table: Map<String, Int>? = null
val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap()
        }
        return _table ?: error("Concurrent modification")
    }

On the JVM, accessing such private backing properties incurs no extra functon call overhead due to compiler optimizations.

Compile-Time Constants

Read-only properties known at compile time can be marked with const if they meet specific criteria:

  • Declared at top level, in an object, or in a companion object
  • Initialized with a String or primitive type
  • Have no custom getter
const val DEPRECATION_MESSAGE = "This feature is deprecated"

@Deprecated(DEPRECATION_MESSAGE)
fun outdatedFunction() { /* ... */ }

Late Initialization

Non-null properties usually require initialization in the constructor. For cases like dependency injection or test setup, use lateinit:

class IntegrationTest {
    lateinit var service: ExternalService

    @BeforeEach
    fun setUp() {
        service = ExternalService()
    }

    @Test
    fun verifyBehavior() {
        service.execute()
    }
}

Starting from Kotlin 1.2, you can check if a lateinit property has been initialized:

if (this::service.isInitialized) {
    println(service.status)
}

Tags: kotlin properties Fields OOP accessors

Posted on Sun, 11 Oct 2026 17:00:26 +0000 by NL_Rosko