Kotlin Access Modifiers

Access modifiers control visibility of classes, objects, interfaces, constructors, functions, properties, and their setters. Kotlin provides public, internal, protected, and private.

1. public (Default)

Visible everywhere. If you omit a modifier, public is assumed.

// default is public
class PublicClass {
  public val x = 1
  fun foo() = x
}
val pc = PublicClass()
println(pc.x)
println(pc.foo())

2. internal

Visible within the same module (e.g., a Gradle project). Hides declarations from other modules.

// in module A
internal class InternalClass {
  internal fun bar() = "Hello"
}

// in module B (cannot see InternalClass)
// val ic = InternalClass() // Error: unresolved reference

3. private (Top-Level)

At top-level (in a file), private restricts visibility to that file only.

// File: Utils.kt
private fun helper() = 42

fun publicApi() = helper()

Usage: helper() is inacces
sible outside Utils.kt;

4. private (Member)

Inside a class, private restricts to that class only (not even subclasses).

class Secret {
  private val code = "1234"
  fun reveal() = code
}

val s = Secret()
// println(s.code) // Error: cannot access private property

5. protected

Visible in the declaring class and its subclasses. Not available at top-level.

open class Base {
  protected val secret = "shh"
}

class Derived: Base() {
  fun show() = secret
}

val d = Derived()
println(d.show())
// println(d.secret) // Error: secret is protected

6. private set

You can expose a read-only val but restrict its setter to private.

class Counter {
  var count: Int = 0
    private set
  fun inc() { count++ }
}

val c = Counter()
c.inc()
println(c.count)  // OK
// c.count = 5    // Error: setter is private

7. Visibility on Constructors

Constructors can have modifiers to control instantiation.

class MyClass private constructor(val x: Int) {
  companion object {
    fun create(x: Int) = MyClass(x)
  }
}

// val m = MyClass(5) // Error: constructor is private
val m2 = MyClass.create(5)

8. local declarations

Declarations inside functions cannot have visibility modifiers—they are always local and invisible outside.

fun foo() {
  val localVar = 10  // always invisible outside foo()
}

9. Combining Modifiers

You may combine private with sealed or abstract to restrict subclassing and visibility.

private sealed class Expr {
  class Num(val value: Int): Expr()
  class Op(val left: Expr, val right: Expr): Expr()
}

fun eval(e: Expr): Int = when(e) {
  is Expr.Num -> e.value
  is Expr.Op  -> eval(e.left) + eval(e.right)
}

10. Summary & Best Practices

- Default to public only for API you intend to expose.
- Use internal to hide module implementation details.
- Favor private to encapsulate implementation and maintain invariants.
- Use protected sparingly—prefer composition over inheritance.
- Restrict setters with private set to control state mutation.
- Control constructors visibility to enforce valid object creation.
- Avoid top-level protected/private—use file-level private only.
- Document visibility decisions in KDoc for clarity.

Previous: Kotlin Interface | Next: Kotlin Extension

<
>