Skip to main content

Kotlin Backing Fields

Introduction

In Kotlin, properties are a powerful feature that allows you to define getters and setters to control how a class's attributes are accessed and modified. When you create custom getters and setters for a property, Kotlin provides a special mechanism called a backing field to store the actual value of the property.

Understanding backing fields is essential for working with Kotlin properties effectively, especially when you need custom behavior while still maintaining state. This guide will explain what backing fields are, when they're automatically generated, and how to use them properly in your Kotlin code.

What is a Backing Field?

A backing field is a special field that Kotlin generates automatically for a property when it's needed. It's accessed using the field identifier within property accessors (getters and setters). The backing field serves as the actual storage for the property's value.

Think of a backing field as the "behind the scenes" storage that holds your property's data while your custom getters and setters control how that data is accessed and modified.

When is a Backing Field Generated?

Kotlin will automatically generate a backing field for a property if:

  1. You use the default getter and/or setter implementation, or
  2. Your custom getter and/or setter references the field identifier

If your property doesn't need to store state (e.g., it calculates its value on the fly), Kotlin won't generate a backing field.

Basic Example of Backing Fields

Let's start with a simple example to understand how backing fields work:

kotlin
class Person {
var name: String = "Unknown"
// Default getter is used
set(value) {
println("Name changing from $field to $value")
field = value // Using the backing field
}
}

fun main() {
val person = Person()
println("Initial name: ${person.name}")

person.name = "John"
println("Updated name: ${person.name}")
}

Output:

Initial name: Unknown
Name changing from Unknown to John
Updated name: John

In this example:

  • We have a name property with the default getter and a custom setter
  • The field identifier within the setter refers to the backing field
  • When we modify the name property, our custom setter is called, and we use field to access the current value and update it

Custom Getters and Setters with Backing Fields

Now let's look at a more complex example with both custom getter and setter:

kotlin
class Temperature {
var celsius: Float = 0.0f
set(value) {
if (value < -273.15f) {
throw IllegalArgumentException("Temperature below absolute zero is not possible")
}
field = value // Update the backing field
}

var fahrenheit: Float
get() = (celsius * 9/5) + 32 // No backing field needed here
set(value) {
celsius = (value - 32) * 5/9 // Converts to celsius and uses celsius's backing field
}
}

fun main() {
val temp = Temperature()
temp.celsius = 25.0f
println("Celsius: ${temp.celsius}°C")
println("Fahrenheit: ${temp.fahrenheit}°F")

temp.fahrenheit = 68.0f
println("Celsius: ${temp.celsius}°C")
println("Fahrenheit: ${temp.fahrenheit}°F")

// This would throw an exception:
// temp.celsius = -300.0f
}

Output:

Celsius: 25.0°C
Fahrenheit: 77.0°F
Celsius: 20.0°C
Fahrenheit: 68.0°F

In this example:

  • celsius has a custom setter that validates the value and uses a backing field to store the value
  • fahrenheit is a computed property for its getter (no backing field needed)
  • fahrenheit's setter updates the celsius property instead of using its own backing field

When Not to Use Backing Fields

Interestingly, not all properties need backing fields. If a property's value is derived from other properties or computations, you may not need to store it separately:

kotlin
class Rectangle(val width: Double, val height: Double) {
val area: Double
get() = width * height // No backing field needed, computed on demand

val isSquare: Boolean
get() = width == height // No backing field needed
}

fun main() {
val rect = Rectangle(5.0, 3.0)
println("Width: ${rect.width}")
println("Height: ${rect.height}")
println("Area: ${rect.area}")
println("Is square? ${rect.isSquare}")
}

Output:

Width: 5.0
Height: 3.0
Area: 15.0
Is square? false

Neither area nor isSquare has a backing field because their values are computed on demand.

Practical Use Cases for Backing Fields

1. Input Validation

One of the most common use cases for backing fields is validating input:

kotlin
class User {
var email: String = ""
set(value) {
if (!value.contains("@")) {
throw IllegalArgumentException("Invalid email format")
}
field = value
}

var age: Int = 0
set(value) {
if (value < 0) {
throw IllegalArgumentException("Age cannot be negative")
}
field = value
}
}

fun main() {
val user = User()
user.email = "[email protected]"
user.age = 25

println("User email: ${user.email}")
println("User age: ${user.age}")

try {
user.email = "invalid-email"
} catch (e: IllegalArgumentException) {
println("Error: ${e.message}")
}
}

Output:

User email: [email protected]
User age: 25
Error: Invalid email format

2. Data Transformation

Backing fields allow you to transform data when it's stored or retrieved:

kotlin
class Profile {
var name: String = ""
set(value) {
field = value.trim().capitalize()
}

var tags: List<String> = listOf()
set(value) {
field = value.map { it.lowercase() }.distinct()
}
}

fun main() {
val profile = Profile()
profile.name = " john smith "
profile.tags = listOf("Kotlin", "Programming", "KOTLIN", "coding")

println("Name: '${profile.name}'")
println("Tags: ${profile.tags}")
}

Output:

Name: 'John Smith'

3. Logging and Debugging

Backing fields are useful for logging property changes:

kotlin
class ConfigSettings {
var debugMode: Boolean = false
set(value) {
println("Debug mode changing from $field to $value")
field = value
}

var maxConnections: Int = 10
set(value) {
println("Maximum connections changing from $field to $value")
field = value
}
}

fun main() {
val config = ConfigSettings()
config.debugMode = true
config.maxConnections = 20
}

Output:

Debug mode changing from false to true
Maximum connections changing from 10 to 20

Common Mistakes with Backing Fields

Recursive Stack Overflow

One common mistake is accidentally creating infinite recursion by referring to the property itself in its accessor:

kotlin
class Person {
// THIS IS INCORRECT - will cause a stack overflow!
var name: String = ""
set(value) {
name = value // This calls the setter again!
}
}

The correct version would use the backing field:

kotlin
class Person {
var name: String = ""
set(value) {
field = value // Use backing field instead
}
}

Forgetting to Use the Backing Field

Another mistake is forgetting to use the backing field when needed:

kotlin
class Counter {
var count: Int = 0
set(value) {
if (value >= 0) {
// Forgetting to assign to field
// The value won't be stored!
}
}
}

The correct version:

kotlin
class Counter {
var count: Int = 0
set(value) {
if (value >= 0) {
field = value // Don't forget to update the field!
}
}
}

Summary

Kotlin backing fields provide a way to store property values when using custom getters and setters. Here's what we covered:

  • Backing fields are automatically generated when needed and accessed via the field identifier
  • They're used in custom property accessors to store and retrieve the actual value
  • Not all properties need backing fields (e.g., computed properties)
  • Common use cases include validation, transformation, and logging
  • Backing fields help avoid recursive stack overflows in custom accessors

Understanding backing fields is essential for creating robust Kotlin classes with proper encapsulation and data handling.

Exercises

  1. Create a BankAccount class with a balance property that doesn't allow negative values
  2. Implement a Password class with a property that enforces minimum length and complexity rules
  3. Create a Circle class with a radius property and computed properties for area and circumference
  4. Implement a Temperature class that stores temperature in Kelvin but provides Celsius and Fahrenheit interfaces

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)