In Kotlin, visibility modifiers control where classes, properties, functions, and constructors can be accessed from.
The main visibility modifiers are:
publicprivateprotectedinternal
If you do not specify a visibility modifier, Kotlin uses public by default.
1. public
public means the declaration can be accessed from anywhere.
class User {
public val name: String = "Alice"
public fun sayHello() {
println("Hello, $name")
}
}
Because public is the default, this is equivalent:
class User {
val name: String = "Alice"
fun sayHello() {
println("Hello, $name")
}
}
Use public when you want something to be part of the normal external API of a class.
2. private
Inside a class, private means the member can only be accessed inside that same class.
class BankAccount {
private var balance: Double = 0.0
fun deposit(amount: Double) {
if (amount > 0) {
balance += amount
}
}
fun getBalance(): Double {
return balance
}
}
Usage:
fun main() {
val account = BankAccount()
account.deposit(100.0)
println(account.getBalance())
// Error:
// println(account.balance)
}
Here, balance is hidden from outside code. The class controls access through deposit() and getBalance().
Private class members
class UserService {
private fun validateName(name: String): Boolean {
return name.isNotBlank()
}
fun createUser(name: String) {
if (validateName(name)) {
println("User created: $name")
}
}
}
validateName() is an implementation detail and cannot be called from outside UserService.
3. protected
protected means the member is visible inside:
- The class where it is declared
- Its subclasses
It is not visible to general outside code.
open class Animal {
protected val name: String = "Animal"
protected fun makeSound() {
println("Some sound")
}
}
class Dog : Animal() {
fun bark() {
println(name)
makeSound()
println("Woof!")
}
}
Usage:
fun main() {
val dog = Dog()
dog.bark()
// Error:
// println(dog.name)
// Error:
// dog.makeSound()
}
Dog can access name and makeSound() because it inherits from Animal, but outside code cannot.
Important note about protected
In Kotlin, protected is mainly for class inheritance. Unlike Java, Kotlin does not make protected members visible to the whole package.
4. internal
internal means the declaration is visible within the same module.
A module is usually something like:
- a Gradle source set
- a Maven project
- an IntelliJ IDEA module
- a set of files compiled together
internal class InternalLogger {
fun log(message: String) {
println("LOG: $message")
}
}
Code in the same module can use it:
fun main() {
val logger = InternalLogger()
logger.log("Application started")
}
But code from another module cannot access InternalLogger.
Use internal when you want something available across your project/module but not exposed as a public API to other modules.
Visibility in class members
You can apply visibility modifiers to properties and functions:
class Profile {
public val username: String = "guest"
private val passwordHash: String = "abc123"
internal val accountId: Int = 42
protected val role: String = "user"
public fun showUsername() {
println(username)
}
private fun checkPassword() {
println(passwordHash)
}
}
However, protected only makes sense in classes that may be inherited from:
open class BaseController {
protected fun authorize() {
println("Checking permissions")
}
}
class UserController : BaseController() {
fun getUser() {
authorize()
println("Returning user")
}
}
Visibility for constructors
You can also control constructor visibility.
Private constructor
Useful when you want to restrict object creation.
class DatabaseConnection private constructor() {
companion object {
fun create(): DatabaseConnection {
return DatabaseConnection()
}
}
}
Usage:
fun main() {
val connection = DatabaseConnection.create()
// Error:
// val direct = DatabaseConnection()
}
Visibility for property setters
A common Kotlin pattern is to expose a property for reading but restrict writing.
class Counter {
var count: Int = 0
private set
fun increment() {
count++
}
}
Usage:
fun main() {
val counter = Counter()
counter.increment()
println(counter.count)
// Error:
// counter.count = 10
}
Here:
countcan be read from outside the classcountcan only be changed inside the class
You can also use protected set or internal set:
open class Document {
var status: String = "draft"
protected set
}
Subclasses can change status, but outside code cannot.
Top-level visibility
Kotlin also allows functions, properties, and classes outside classes.
private fun helper() {
println("Only visible in this file")
}
internal fun moduleHelper() {
println("Visible in the same module")
}
public fun publicApi() {
println("Visible everywhere")
}
For top-level declarations:
| Modifier | Meaning |
|---|---|
public |
Visible everywhere |
private |
Visible only in the same file |
internal |
Visible in the same module |
protected |
Not allowed at top level |
Quick comparison
| Modifier | Class member visibility |
|---|---|
public |
Visible everywhere |
private |
Visible only inside the declaring class |
protected |
Visible inside the declaring class and subclasses |
internal |
Visible inside the same module |
For top-level declarations:
| Modifier | Top-level visibility |
|---|---|
public |
Visible everywhere |
private |
Visible only in the same file |
internal |
Visible inside the same module |
protected |
Not allowed |
Practical example
open class Account(
public val owner: String,
private var balance: Double
) {
internal val accountNumber: String = "ACC-123"
protected fun canWithdraw(amount: Double): Boolean {
return amount <= balance
}
fun deposit(amount: Double) {
if (amount > 0) {
balance += amount
}
}
fun currentBalance(): Double {
return balance
}
}
class SavingsAccount(owner: String, balance: Double) : Account(owner, balance) {
fun withdraw(amount: Double) {
if (canWithdraw(amount)) {
println("Withdrawal allowed")
} else {
println("Insufficient funds")
}
}
}
Usage:
fun main() {
val account = SavingsAccount("Alice", 500.0)
println(account.owner)
println(account.currentBalance())
account.deposit(100.0)
account.withdraw(50.0)
// Error: private
// println(account.balance)
// Error: protected
// account.canWithdraw(100.0)
// Accessible only in the same module:
// println(account.accountNumber)
}
Rule of thumb
Use the most restrictive visibility that still works:
- Use
privatefor implementation details. - Use
protectedfor behavior intended only for subclasses. - Use
internalfor code shared inside a module but hidden from external modules. - Use
publicfor APIs that other code is expected to use.
In most Kotlin classes, you will commonly use private for internal state and leave only the necessary functions or properties public.
