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
The compiler automatically generates backing fields for properties when a value needs to be stored in memory.
For example, the compiler creates a backing field when you use the default get() and set() functions because they read and write the stored value:
You can access backing fields by using the field keyword in a custom get() or set() function. For example, you can add extra logic to a getter or setter, or trigger an additional action when a property changes.
In this example, the score property uses the backing field inside the set() function so that updating the value also triggers a log event:
Backing fields aren't created by default for all properties because they might not need them. For example, the isEmpty property doesn't have a backing field because the value is calculated from the size property each time you access it:
Explicit backing fields
Sometimes you might need more flexibility. 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 an explicit backing field.
In the following example, the ShoppingCart class has an items property that represents everything in the shopping cart. The class exposes the items property as a read-only list of strings, but internally it stores the data in a mutable list with an explicit backing field:
In this example, the compiler infers the type of the backing field from the mutableListOf() call: MutableList<String>. You can also declare the type of the backing field explicitly:
In the example of the ShoppingCart class, the compiler smart casts the items property to the MutableList<String> type, so the class can add and remove items from the cart through the add() and remove() functions. Outside the class, the compiler uses the public property type List<String>, so API users can only read what's in the items list.
Limitations
To use explicit backing fields, their properties and the backing fields themselves must follow certain rules. Properties can have explicit backing fields only if they:
Don't have a custom getter.
Are read-only (
val).Aren't
open.Aren't a delegated property.
Aren't compile-time constants.
In addition, the backing field type must be a subtype of the property's type and have private visibility.
You can work around these restrictions by using backing properties instead.
Backing properties
If explicit backing fields don't fit your use case, you can try using a coding pattern called a backing property.
For example, if your property needs a custom getter:
In this example, the UserDirectory class has a read-only users property that lists every user in the directory. The _users variable is the private backing property containing the real list. The getter for the public users property sorts the entries before returning them.
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
objectdeclaration or a companion object.They must be initialized with a value of type
Stringor 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.