Kotlin private keyword tutorial shows how to use private access modifier in Kotlin. Learn about class members, properties, and functions visibility with examples.
last modified April 19, 2025
Kotlin’s access modifiers control the visibility of classes, objects, and members. The private keyword is the most restrictive modifier. This tutorial explores the private keyword in depth with practical examples.
The private keyword in Kotlin restricts visibility to the declaring scope. A private member is only accessible within the same class or file. This promotes encapsulation by hiding implementation details.
Private properties can only be accessed within their declaring class. This prevents external code from directly modifying internal state.
PrivateProperty.kt
package com.zetcode
class BankAccount { private var balance: Double = 0.0
fun deposit(amount: Double) {
if (amount > 0) {
balance += amount
}
}
fun getBalance(): Double = balance
}
fun main() { val account = BankAccount() account.deposit(100.0) println(account.getBalance()) // Output: 100.0
// account.balance = 500.0 // Error: Cannot access 'balance'
}
Here, balance is private and can only be modified through the deposit method. Direct access from outside the class is prevented, ensuring controlled modifications to the balance.
Private functions are helper methods only accessible within their declaring class. They help break down complex operations into smaller steps.
PrivateFunction.kt
package com.zetcode
class Calculator { fun add(a: Int, b: Int): Int { validateInput(a, b) return a + b }
private fun validateInput(a: Int, b: Int) {
require(a >= 0 && b >= 0) {
"Inputs must be non-negative"
}
}
}
fun main() { val calc = Calculator() println(calc.add(5, 3)) // Output: 8
// calc.validateInput(2, 2) // Error: Cannot access 'validateInput'
}
The validateInput function is private and used internally by the add method. External code cannot call this validation function directly, keeping the validation logic encapsulated.
A private constructor prevents direct instantiation of a class. This is useful for singleton patterns or factory methods.
PrivateConstructor.kt
package com.zetcode
class Database private constructor() { companion object { private var instance: Database? = null
fun getInstance(): Database {
if (instance == null) {
instance = Database()
}
return instance!!
}
}
fun query(sql: String) = println("Executing: $sql")
}
fun main() { val db = Database.getInstance() db.query(“SELECT * FROM users”)
// val db2 = Database() // Error: Cannot access ''
}
The private constructor forces clients to use getInstance to get a Database object. This ensures only one instance exists (singleton pattern). The instance variable is also private to prevent external modification.
When used in top-level declarations (outside any class), private restricts visibility to the current file. This is useful for file-specific utilities.
TopLevelPrivate.kt
package com.zetcode
private const val MAX_RETRIES = 3
private fun logError(message: String) { println(“ERROR: $message”) }
class NetworkClient { fun fetchData() { var attempts = 0 while (attempts < MAX_RETRIES) { try { // Network operation return } catch (e: Exception) { logError(e.message ?: “Unknown error”) attempts++ } } } }
// In another file: // fun test() { // println(MAX_RETRIES) // Error: Cannot access ‘MAX_RETRIES’ // logError(“Test”) // Error: Cannot access ’logError' // }
Both MAX_RETRIES constant and logError function are private to this file. They can be used within the file but are inaccessible from other files, even in the same package.
Properties can have private setters while keeping the getter public. This allows read-only access to external code while permitting internal modifications.
PrivateSetter.kt
package com.zetcode
class User(name: String) { var name: String = name private set
fun changeName(newName: String) {
if (newName.isNotBlank()) {
name = newName
}
}
}
fun main() { val user = User(“Alice”) println(user.name) // Output: Alice
user.changeName("Bob")
println(user.name) // Output: Bob
// user.name = "Charlie" // Error: Cannot assign to 'name'
}
The name property has a public getter but private setter. External code can read the name but only modify it through the changeName method, which can enforce validation rules.
Private members in nested classes behave similarly to regular classes, with visibility limited to the nested class scope.
NestedClassPrivate.kt
package com.zetcode
class Outer { private val outerSecret = “Outer secret”
inner class Inner {
private val innerSecret = "Inner secret"
fun revealSecrets() {
println(outerSecret) // Can access outer's private members
println(innerSecret)
}
}
fun testInner() {
val inner = Inner()
inner.revealSecrets()
// println(inner.innerSecret) // Error: Cannot access 'innerSecret'
}
}
fun main() { val outer = Outer() outer.testInner()
// println(outer.outerSecret) // Error: Cannot access 'outerSecret'
}
The inner class can access its outer class’s private members, but private members of the inner class are not accessible to the outer class. Both classes' private members are inaccessible from outside the Outer class.
Kotlin interfaces can have private functions starting from Kotlin 1.4. These functions are only accessible within the interface and its implementations.
InterfacePrivate.kt
package com.zetcode
interface Logger { fun log(message: String)
private fun formatMessage(message: String): String {
return "[${System.currentTimeMillis()}] $message"
}
fun logWithTimestamp(message: String) {
log(formatMessage(message))
}
}
class ConsoleLogger : Logger { override fun log(message: String) { println(message) } }
fun main() { val logger = ConsoleLogger() logger.logWithTimestamp(“Hello, Kotlin!”)
// logger.formatMessage("Test") // Error: Cannot access 'formatMessage'
}
The formatMessage function is private to the Logger interface. It can be used by other interface members like logWithTimestamp but isn’t accessible to implementing classes or external code.
Default to private: Make members private by default and only expose what’s necessary. Encapsulate state: Use private properties with controlled access methods to maintain object integrity. Hide implementation: Keep helper methods private to simplify your public API. Consider visibility: Choose the most restrictive visibility that still allows necessary access. Document public API: Focus documentation on public members since private ones are implementation details.
Kotlin Visibility Modifiers Documentation
This tutorial covered Kotlin’s private keyword in depth, showing various applications including properties, functions, constructors, and nested classes. Proper use of private visibility promotes encapsulation and creates more maintainable code by hiding implementation details.
My name is Jan Bodnar, and I am a passionate programmer with many years of programming experience. I have been writing programming articles since 2007. So far, I have written over 1400 articles and 8 e-books. I have over eight years of experience in teaching programming.
List all Kotlin tutorials.