Properties
In Kotlin, properties let you store and manage data without writing functions to access or change the data. You can use properties in classes, interfaces, objects, companion objects, and even outside these structures as top-level properties.
Every property has a name, a type, and an automatically generated get()
function called a getter. You can use the getter to read the property's value. If the property is mutable, it also has a set()
function called a setter, which allows you to change the property's value.
Declaring properties
Properties can be mutable (var
) or read-only (val
). You can declare them as a top-level property in a .kt
file. Think of a top-level property as a global variable that belongs to a package:
You can also declare properties inside a class, interface, or object:
To use a property, refer to it by its name:
In Kotlin, we recommend initializing properties when you declare them to keep your code safe and easy to read. However, you can initialize them later in special cases.
Declaring the property type is optional if the compiler can infer it from the initializer or the getter's return type:
Custom getters and setters
By default, Kotlin automatically generates getters and setters. You can define your own custom accessors when you need extra logic, such as validation, formatting, or calculations based on other properties.
A custom getter runs every time the property is accessed:
You can omit the type if the compiler can infer it from the getter:
A custom setter runs every time you assign a value to the property, except during initialization. By convention, the name of the setter parameter is value
, but you can choose a different name:
Changing visibility or adding annotations
In Kotlin, you can change accessor visibility or add annotations without replacing the default implementation. You don't have to make these changes within a body {}
.
To change the visibility of an accessor, use the modifier before the get
or set
keyword:
To annotate an accessor, use the annotation before the get
or set
keyword:
This example uses reflection to show which annotations are present on the getter and setter.
Backing fields
In Kotlin, accessors use backing fields to store the property's value in memory. Backing fields are useful when you want to add extra logic to a getter or setter, or when you want to trigger an additional action whenever the property changes.
You can't declare backing fields directly. Kotlin generates them only when necessary. You can reference the backing field in accessors using the field
keyword.
Kotlin only generates backing fields if you use the default getter or setter, or if you use field
in at least one custom accessor.
For example, the isEmpty
property has no backing field because it uses a custom getter without the field
keyword:
In this example, the score
property has a backing field because the setter uses the field
keyword:
Backing properties
Sometimes you might need more flexibility than using a backing field can provide. For example, if you have an API where you want to be able to modify the property internally but not externally. In such cases, you can use a coding pattern called a backing property.
In the following example, the ShoppingCart
class has an items
property that represents everything in the shopping cart. You want the items
property to be read-only outside the class but still allow one "approved" way for the user to modify the items
property directly. To achieve this, you can define a private backing property called _items
and a public property called items
that delegates to the backing property's value.
In this example, the user can only add items to the cart through the addItem()
function, but can still access the items
property to see what's inside.
On the JVM, the compiler optimizes access to private properties with default accessors to avoid function call overhead.
Backing properties are also useful when you want more than one public property to share a state. For example:
In this example, the _celsius
backing property is accessed by both the celsius
and fahrenheit
properties. This setup provides a single source of truth with two public views.
Compile-time constants
If the value of a read-only property is known at compile time, mark it as a compile-time constant using the const
modifier. Compile-time constants are inlined at compile time, so each reference is replaced with its actual value. They are accessed more efficiently because no getter is called:
Compile-time constants must meet the following requirements:
They must be either a top-level property, or a member of an
object
declaration or a companion object.They must be initialized with a value of type
String
or a primitive type.They can't have a custom getter.
Compile-time constants still have a backing field, so you can interact with them using reflection.
You can also use these properties in annotations:
Late-initialized properties and variables
Normally, you must initialize properties in the constructor. However, this isn't always convenient. For example, you might initialize properties through dependency injection or inside the setup method of a unit test.
To handle these situations, mark the property with the lateinit
modifier:
You can use the lateinit
modifier on var
properties declared as:
Top-level properties.
Local variables.
Properties inside the body of a class.
For class properties:
You can't declare them in the primary constructor.
They must not have a custom getter or setter.
In all cases, the property or variable must be non-nullable and must not be a primitive type.
If you access a lateinit
property before initializing it, Kotlin throws a specific exception that identifies the uninitialized property being accessed:
To check whether a lateinit var
has already been initialized, use the isInitialized
property on the reference to that property:
You can only use isInitialized
on a property if you can already access that property in your code. The property must be declared in the same class, in an outer class, or as a top-level property in the same file.
Overriding properties
Delegated properties
To reuse logic and reduce code duplication, you can delegate the responsibility of getting and setting a property to a separate object.
Delegating accessor behavior keeps the property's accessor logic centralized, making it easier to reuse. This approach is useful when implementing behaviors like:
Computing a value lazily.
Reading from a map by a given key.
Accessing a database.
Notifying a listener when a property is accessed.
You can implement these common behaviors in libraries yourself or use existing delegates provided by external libraries. For more information, see delegated properties.