Question
In Java, I can write getters and setters manually or generate them with an IDE or Lombok annotations such as @Getter.
Kotlin properties provide getters and setters automatically, but I do not understand how to define and use custom accessors. For example, I want a private value with a getter similar to Java:
private val isEmpty: String
get() = this.toString()
However, making the getter public produces the error: Getter visibility must be the same as property visibility. How do getters and setters work in Kotlin, and how can I expose a computed value safely?
Short Answer
Kotlin treats a property as its public API rather than making you call getter and setter methods explicitly. You will learn how Kotlin generates accessors, how to write custom getters and setters, why a private property cannot have a public getter, and how backing properties provide controlled public access.
Concept
In Kotlin, a property combines a value and its accessors:
val name: String = "Ada"
A val has a getter. A var has both a getter and a setter. Kotlin normally generates those accessors for you.
val name: String = "Ada" // getter only
var age: Int = 20 // getter and setter
From Kotlin, you use property syntax instead of Java-style method calls:
println(person.name) // Calls the getter
person.age = 21 // Calls the setter
On the JVM, Kotlin usually creates Java-compatible getter and setter methods behind the scenes. For example, Java code can commonly call getName() and setAge(...).
A custom getter calculates or transforms a value when the property is read:
val message: String
get() = "Hello"
The property's visibility is also the visibility of its getter. A private property is intentionally unavailable outside its class, so Kotlin does not allow that same property's getter to become public. If outside code could call the getter, the property would not really be private.
To expose a computed value, make the exposed property public. To hide stored implementation data while exposing a safe read-only value, use a separate private backing property and a public val.
Mental Model
Think of a property as a labelled control panel on a machine.
- A getter is the display: it lets someone read a value.
- A setter is the input control: it lets someone change a value.
valmeans the display is available, but there is no public input control.varmeans both display and input controls exist.privatemeans the entire control panel is inside the machine. People outside cannot use its getter either.
If you need an internal machine component but want to show selected information externally, keep the component in a private box and create a separate public display for it.
Syntax and Examples
A property with Kotlin's default getter:
class User {
val name: String = "Ada"
}
fun main() {
val user = User()
println(user.name)
}
name is a val, so callers can read it but cannot assign a new value.
class Counter {
var count: Int = 0
}
fun main() {
val counter = Counter()
counter.count = 1
println(counter.count)
}
count is a var, so Kotlin provides both a getter and a setter.
Custom getter
Use get() when the value should be calculated as it is read:
class Greeting(private val firstName: String, private val lastName: String) {
val fullName: String
get() = "$firstName $lastName"
}
fun main() {
val greeting = Greeting("Ada", "Lovelace")
println(greeting.fullName)
}
fullName does not need stored data of its own. Its getter creates the result each time it is accessed.
For the question's example, use a public computed property:
class Example {
val textRepresentation: String
get() = toString()
}
A name beginning with is usually represents a Boolean in Kotlin. For example:
class Basket(private val items: List<String>) {
val isEmpty: Boolean
get() = items.isEmpty()
}
Custom setter
A custom setter can validate or normalize incoming values. The special name field refers to the property's backing field:
class Profile {
var username: String = "guest"
set(value) {
require(value.isNotBlank()) { "Username cannot be blank" }
field = value.trim()
}
}
field is available only inside an accessor. It avoids recursively assigning to the setter itself.
Step by Step Execution
Consider this class:
class Temperature {
var celsius: Double = 0.0
set(value) {
require(value >= -273.15) { "Temperature is below absolute zero" }
field = value
}
val fahrenheit: Double
get() = celsius * 9 / 5 + 32
}
fun main() {
val temperature = Temperature()
temperature.celsius = 20.0
println(temperature.fahrenheit)
}
Execution flow:
Temperature()creates an object.celsiusstarts as0.0.temperature.celsius = 20.0calls the custom setter.- The setter checks that
20.0is not below-273.15. field = valuestores20.0in the backing field forcelsius.temperature.fahrenheitcalls its custom getter.- The getter reads
celsius, calculates20.0 * 9 / 5 + 32, and returns68.0. printlnprints68.0.
No value is stored for fahrenheit; it is calculated whenever it is requested.
Real World Use Cases
Custom accessors are useful when reading or changing a value needs rules.
- Read-only API state: Store mutable data privately but expose a public
valso callers can inspect it without modifying it. - Validation: Reject invalid values such as blank usernames, negative quantities, or impossible dates in a setter.
- Normalization: Trim whitespace, convert a code to uppercase, or constrain a numeric value before storing it.
- Computed UI values: Build a display name, total price, or formatted label from other properties.
- Derived data: Calculate whether an order can be shipped from inventory and payment status.
- Encapsulation: Change the internal storage later without changing the public property used by other code.
Example: exposing an immutable view of an internally mutable collection:
class ShoppingCart {
private val mutableItems = mutableListOf<String>()
val items: List<String>
get() = mutableItems.toList()
fun add(item: String) {
mutableItems.add(item)
}
}
Code outside the class can read items, but it cannot modify the cart's internal list.
Real Codebase Usage
In real Kotlin projects, accessors are usually kept small and predictable. Common patterns include the following.
Public read, restricted write
Use a var with a private setter when other code may read a value but only the class may change it:
class Download {
var progress: Int = 0
private set
fun updateProgress(newProgress: Int) {
progress = newProgress.coerceIn(0, 100)
}
}
This is often preferable to a public setter because it protects the object's invariants.
Private backing property and public read-only property
Use a private mutable value plus a public immutable view:
class Session {
private var _token: String? = null
val isAuthenticated: Boolean
get() = _token != null
fun signIn(token: String) {
_token = token
}
fun signOut() {
_token = null
}
}
Derived properties instead of duplicated state
Avoid storing values that can be reliably calculated from other values:
class Invoice(val subtotal: Double, val taxRate: Double) {
val total: Double
get() = subtotal * (1 + taxRate)
}
This prevents total from becoming out of sync with subtotal or taxRate.
Guard clauses in setters
For simple validation, require clearly communicates that invalid input is a caller error:
var quantity: Int = 1
set(value) {
require(value > 0) { "Quantity must be positive" }
field = value
}
Common Mistakes
Trying to make a private property's getter public
This does not work:
class Example {
private val secret: String
public get() = "visible"
}
A private property cannot expose a public getter. Instead, define a separate public property:
class Example {
private val secret = "visible"
val displayValue: String
get() = secret
}
Using var when callers should not assign values
If a value should be read-only from outside the class, prefer val:
val fullName: String
get() = "$firstName $lastName"
Or use a private setter when the class needs to update it internally:
var status: String = "New"
private set
Recursive setter assignment
This setter calls itself forever:
var score: Int = 0
set(value) {
score = value // Incorrect: calls the setter again
}
Use field to assign the backing field:
var score: Int = 0
set(value) {
field = value
}
Expecting field in every getter
A computed property may not have a backing field:
val currentTime: Long
get() = System.currentTimeMillis()
Use field only when the property actually stores a value and the accessor needs that stored value.
Naming a non-Boolean value isEmpty
By convention, isEmpty should be Boolean:
val isEmpty: Boolean
get() = items.isEmpty()
Use names such as text, description, or displayValue for String properties.
Comparisons
| Feature | val | var | Custom getter | Custom setter |
|---|---|---|---|---|
| Can be read | Yes | Yes | Yes | Yes |
| Can be assigned after initialization | No | Yes | Depends on whether it is a var | Yes, if setter is accessible |
| Has a getter | Yes | Yes | You define its behavior | Uses the getter normally |
| Has a setter | No | Yes | No effect by itself | You define assignment behavior |
| Typical use | Read-only values | Mutable state | Derived or formatted values | Validation and controlled updates |
| Approach | Best when | Example |
|---|---|---|
Public val | The value is public and read-only | val fullName get() = ... |
var with private set | Everyone may read, but only the class may change it | var progress = 0; private set |
Private backing property + public val | Internal data and public API should be separate | private var _items; val items get() = ... |
Public var | External callers are allowed to update state directly | Form input or simple data holder |
A Kotlin property is similar to Java fields plus getter/setter methods, but Kotlin presents them as one language feature. Kotlin callers normally use object.property, even when custom accessor code runs.
Cheat Sheet
// Read-only property: getter is generated
val name: String = "Ada"
// Mutable property: getter and setter are generated
var age: Int = 20
// Computed read-only property
val fullName: String
get() = "$firstName $lastName"
// Public read, class-only write
var status: String = "New"
private set
// Validate before storing
var quantity: Int = 1
set(value) {
require(value > 0)
field = value
}
// Private storage, public read-only view
private val _items = mutableListOf<String>()
val items: List<String>
get() = _items.toList()
Rules to remember:
valhas a getter but no setter.varhas both a getter and setter.- Use
get()for calculated or transformed reads. - Use
set(value)for validation or transformation during assignment. - Use
fieldinside an accessor to access stored property data. - A private property cannot have a public getter.
- Use
private setto allow public reads and private writes. - Prefer
isSomething: Booleanfor names beginning withis.
FAQ
Does Kotlin automatically create getters and setters?
Yes. Every val has a getter, and every var has both a getter and a setter unless you define custom behavior.
How do I call a getter in Kotlin?
Use property syntax: user.name. Do not normally write user.getName() in Kotlin.
Can a private Kotlin property have a public getter?
No. The getter's visibility must match the property's visibility. Create a separate public property that returns the private value instead.
How can I make a property readable publicly but writable only inside the class?
Use var with private set:
var state = "idle"
private set
What is field in a Kotlin getter or setter?
field is the property's backing field. It is available only inside an accessor and lets you read or write stored data without recursively calling the accessor.
Should I use a custom getter for every property?
No. Use the default generated accessors unless you need computed output, validation, transformation, or access control.
Does a computed val store its result?
Usually not. A getter such as get() = firstName + lastName calculates the result every time it is read unless you explicitly store or cache it.
Mini Project
Description
Build a small Account class that protects its internal balance while exposing useful derived information. The project demonstrates a private backing property, a public read-only property, a custom computed getter, and controlled methods for state changes.
Goal
Create an account object that allows deposits and withdrawals while preventing outside code from assigning the balance directly.
Requirements
- Store the account balance as private mutable state.
- Expose a public read-only
balanceproperty. - Add an
isOverdrawnBoolean computed property. - Provide a
depositfunction that accepts only positive amounts. - Provide a
withdrawfunction that accepts only positive amounts. - Print the final balance and overdraft status.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.