Kotlin Property Keyword
last modified April 19, 2025
Kotlin's property system provides a concise way to manage class state. The
property
concept replaces fields with built-in accessors. This
tutorial explores properties in depth with practical examples.
Basic Definitions
In Kotlin, a property is a class member that combines a field with accessors.
Properties automatically generate getters and setters. They can be declared with
val
(read-only) or var
(mutable). Properties provide
encapsulation without boilerplate code.
Basic Property Declaration
The simplest property declaration consists of a type and optional initializer. Kotlin automatically provides default accessors. Here's a basic example of property usage.
package com.zetcode class Person { var name: String = "John Doe" val age: Int = 30 } fun main() { val person = Person() println(person.name) // Output: John Doe person.name = "Jane Smith" // Setter called println(person.name) // Output: Jane Smith }
This example shows a mutable name
property and read-only
age
property. The name
can be changed, while
age
cannot. Kotlin generates default getters and setters
automatically.
Custom Getters and Setters
Properties can have custom accessors defined after the property declaration.
The get
and set
blocks allow custom logic. The
setter parameter is conventionally named value
.
package com.zetcode class Rectangle(val width: Int, val height: Int) { val area: Int get() = width * height var borderColor: String = "black" set(value) { if (value in listOf("black", "red", "blue")) { field = value } } } fun main() { val rect = Rectangle(10, 20) println(rect.area) // Output: 200 rect.borderColor = "red" println(rect.borderColor) // Output: red }
Here area
is a computed property with a custom getter.
borderColor
has a setter with validation. The field
identifier refers to the backing field. Invalid colors are silently ignored.
Backing Fields
Kotlin automatically provides backing fields when needed, accessed via the
field
identifier. Backing fields store property values when custom
accessors reference them. They're only generated if used in accessors.
package com.zetcode class Counter { var count: Int = 0 set(value) { if (value >= 0) { field = value } } val isZero: Boolean get() = count == 0 } fun main() { val counter = Counter() counter.count = 5 println(counter.count) // Output: 5 counter.count = -3 // Ignored println(counter.count) // Output: 5 println(counter.isZero) // Output: false }
The count
property uses a backing field to store its value. The
setter validates input, ignoring negative numbers. isZero
is a
computed property without backing field as it doesn't store state.
Late-Initialized Properties
Properties marked with lateinit
can be initialized after declaration.
They must be var
and non-nullable. Useful when dependency injection
or test setup initializes properties.
package com.zetcode class Service { lateinit var apiClient: ApiClient fun initialize(client: ApiClient) { apiClient = client } fun callService() { if (::apiClient.isInitialized) { apiClient.call() } } } class ApiClient { fun call() = println("API called") } fun main() { val service = Service() service.initialize(ApiClient()) service.callService() // Output: API called }
apiClient
is declared without initializer but must be set before use.
The isInitialized
check prevents UninitializedPropertyAccessException.
This pattern is common in frameworks like Spring.
Delegated Properties
Kotlin supports property delegation using the by
keyword. Delegates
handle property access logic. Common delegates include lazy
,
observable
, and vetoable
.
package com.zetcode import kotlin.properties.Delegates class User { val lazyValue: String by lazy { println("computed!") "Hello" } var name: String by Delegates.observable("<no name>") { prop, old, new -> println("$old -> $new") } } fun main() { val user = User() println(user.lazyValue) // Output: computed! then Hello println(user.lazyValue) // Output: Hello (cached) user.name = "John" // Output: <no name> -> John user.name = "Jane" // Output: John -> Jane }
lazyValue
is computed only on first access and cached.
name
notifies when changed via observable delegate. Delegates
encapsulate common property patterns reducing boilerplate code.
Property Visibility
Property visibility modifiers control access to properties and their accessors. Getters inherit property visibility, while setters can have separate modifiers. This enables flexible encapsulation.
package com.zetcode class BankAccount { var balance: Double = 0.0 private set fun deposit(amount: Double) { if (amount > 0) { balance += amount } } fun withdraw(amount: Double): Boolean { if (amount <= balance) { balance -= amount return true } return false } } fun main() { val account = BankAccount() account.deposit(100.0) println(account.balance) // Output: 100.0 // account.balance = 200.0 // Compile error }
The balance
property has a public getter but private setter. Changes
must go through controlled methods deposit
and withdraw
.
This ensures proper validation and business rules.
Extension Properties
Kotlin allows adding properties to existing classes via extensions. Extension
properties don't have backing fields. They must define explicit getters (and
setters for var
).
package com.zetcode val String.hasDigits: Boolean get() = this.any { it.isDigit() } var StringBuilder.lastChar: Char get() = this[this.length - 1] set(value) { this.setCharAt(this.length - 1, value) } fun main() { val text = "Hello123" println(text.hasDigits) // Output: true val builder = StringBuilder("Kotlin") builder.lastChar = '!' println(builder) // Output: Kotli! }
hasDigits
is an extension property checking for digits in Strings.
lastChar
extends StringBuilder with mutable last character access.
Extension properties enhance existing types without inheritance.
Best Practices for Properties
- Prefer properties over Java-style get/set methods: Kotlin's properties are more concise and idiomatic.
- Use custom accessors wisely: Keep complex logic in methods rather than bloating property accessors.
- Consider immutability: Use
val
for properties that shouldn't change after initialization. - Validate in setters: Use property setters for simple validation logic.
- Leverage delegates: Use built-in delegates for common patterns like lazy initialization or observables.
Source
Kotlin Properties Documentation
This tutorial covered Kotlin properties in depth, showing declaration, accessors, delegates, and extensions. Properties provide a powerful way to manage class state with concise syntax. Proper use of properties leads to cleaner, more maintainable Kotlin code.
Author
List all Kotlin tutorials.