Kotlin val Keyword
last modified April 19, 2025
Kotlin's val keyword is used to declare immutable variables. These
variables can be assigned only once and cannot be reassigned. This tutorial
explores the val keyword in depth with practical examples.
Basic Definitions
The val keyword in Kotlin declares read-only variables. Once
initialized, a val cannot be reassigned. It's similar to final
variables in Java. val provides immutability at the reference
level.
Basic val Declaration
The simplest use of val is to declare an immutable variable with
an initial value. The type can be explicitly declared or inferred by Kotlin.
package com.zetcode
fun main() {
val name = "John Doe" // Type inferred as String
val age: Int = 30 // Explicit type declaration
println(name) // Output: John Doe
println(age) // Output: 30
}
Here we declare two val variables. The first has inferred type,
while the second has explicit type. Both variables cannot be reassigned after
initialization. This ensures their values remain constant.
val vs var
Kotlin has both val (immutable) and var (mutable)
keywords. val provides safety by preventing accidental reassignment.
package com.zetcode
fun main() {
val immutable = "I cannot change"
var mutable = "I can change"
// immutable = "New value" // Compilation error
mutable = "New value" // Allowed
println(immutable) // Output: I cannot change
println(mutable) // Output: New value
}
This example shows the key difference between val and var.
Attempting to reassign a val results in a compilation error. Always
prefer val when the value shouldn't change.
val with Custom Types
val can be used with any Kotlin type, including custom classes.
The reference is immutable, but the object's properties might still be mutable.
package com.zetcode
class Person(var name: String, var age: Int)
fun main() {
val person = Person("Alice", 25)
// person = Person("Bob", 30) // Error: val cannot be reassigned
person.name = "Alice Smith" // Allowed: object is mutable
person.age = 26
println("${person.name}, ${person.age}") // Output: Alice Smith, 26
}
Here we declare a val holding a Person object. While we can't
reassign the person variable, we can modify its properties because
the Person class uses var properties. This shows val
only protects the reference.
val in Function Parameters
Function parameters in Kotlin are always immutable, similar to being declared
with val. You cannot reassign them within the function body.
package com.zetcode
fun printMessage(message: String) {
// message = "New message" // Error: parameters are vals
println(message)
}
fun main() {
val msg = "Hello, Kotlin!"
printMessage(msg) // Output: Hello, Kotlin!
}
The message parameter behaves like a val inside the
function. Attempting to reassign it causes a compilation error. This enforces
immutability of function parameters.
val with Lazy Initialization
val can be initialized later using lateinit (for var)
or by using a custom getter. This is useful when initialization is deferred.
package com.zetcode
class Configuration {
val apiKey: String by lazy {
// Complex initialization
System.getenv("API_KEY") ?: "default-key"
}
}
fun main() {
val config = Configuration()
println(config.apiKey) // Initialized on first access
}
Here apiKey is a val that's initialized lazily. The
value is computed only when first accessed. This pattern is useful for expensive
initializations. The value remains constant after initialization.
val in Companion Objects
val in companion objects creates class-level constants. These are
initialized when the class is loaded and remain constant throughout execution.
package com.zetcode
class MathConstants {
companion object {
val PI = 3.14159
val E = 2.71828
}
}
fun main() {
println(MathConstants.PI) // Output: 3.14159
println(MathConstants.E) // Output: 2.71828
}
The PI and E values are class-level constants. They're
accessed via the class name and cannot be modified. This is a common pattern for
mathematical constants and configuration values.
val with Collections
When using val with collections, the reference is immutable but the
collection content might still be mutable. Use immutable collections for complete
immutability.
package com.zetcode
fun main() {
val mutableList = mutableListOf(1, 2, 3)
val immutableList = listOf(1, 2, 3)
mutableList.add(4) // Allowed
// immutableList.add(4) // Error: immutable collection
println(mutableList) // Output: [1, 2, 3, 4]
println(immutableList) // Output: [1, 2, 3]
}
This shows that val only makes the reference immutable. The
collection's mutability depends on its type. For complete immutability, use
listOf, setOf, or mapOf.
Best Practices for val Usage
- Prefer val over var: Use
valby default and only usevarwhen mutation is necessary. - Use for constants:
valis ideal for values that shouldn't change during execution. - Combine with immutable collections: Pair
valwith immutable collections for complete immutability. - Consider visibility: Use
private valfor implementation details andpublic valfor exposed constants. - Document complex vals: Add comments when
valinitialization is non-trivial or has side effects.
Source
Kotlin Variables Documentation
This tutorial covered Kotlin's val keyword in depth, showing its
usage for immutable variables. We explored various scenarios including custom
types, function parameters, and collections. Proper use of val can
make your code safer and more predictable.
Author
List all Kotlin tutorials.