skip to content

In Kotlin, what are the default visibility and inheritance modifiers for a top-level class declared with just `class Foo`, and why were those defaults chosen?

level: juniorimportance: must knowfreq 78%

answer

  1. public + final by default
  2. open to allow subclassing, override to override
  3. members final too, open them individually
  4. Effective Java Item 19 — fragile base class
  5. no package-private; internal = module scope

basics

~10 s

A plain Kotlin class is public so any code can use it, and final so nobody can subclass it. To allow subclassing you must write the keyword open.

solid answer

~40 s

By default a top-level `class Foo` is `public` (visible everywhere) and `final` (cannot be subclassed or have its members overridden). This is the opposite of Java, where classes are subclassable unless marked `final`. To permit inheritance you mark the class `open`, and each method/property you want overridable must also be `open`; the overriding member uses `override`. The defaults follow Joshua Bloch's 'design and document for inheritance or else prohibit it' — final-by-default prevents fragile-base-class bugs from accidental overriding. Visibility options are `public` (default), `internal` (same Gradle module), `protected` (subclasses), and `private`. There is no package-private. `internal` replaces Java's package-private at the module granularity.

code

kotlin · 9 lines
kotlin
open class Repository {            // open: subclassable
    open fun find(id: Int) = id    // open: overridable
    fun version() = 1              // final: locked
}

class CachedRepo : Repository() {
    override fun find(id: Int) = id * 2
    // override fun version() ... // ERROR: version is final
}

go deeper

for a junior

States the two defaults (public, final) and that open enables subclassing.

for a middle

Adds member-level finality, override being implicitly open, and the visibility ladder including internal.

for a senior

Explains the fragile-base-class rationale and the all-open/kotlin-spring plugin for proxy-based frameworks.

for a principal

Frames final-by-default as an API-evolution and binary-compatibility policy, weighing it against framework needs and module-boundary design.

## The two defaults When you write `class Foo`, Kotlin applies two implicit modifiers: - **Visibility = `public`** — the class is visible to all other code that can see its declaring scope. Kotlin's visibility modifiers are: - `public` (default): visible everywhere. - `internal`: visible within the same *module* (a Gradle/Maven compilation unit). This replaces Java's package-private but at module granularity. - `protected`: visible in the declaring class and its subclasses (not available on top-level declarations). - `private`: visible in the declaring file (for top-level) or the declaring class. - There is **no package-private** in Kotlin. - **Inheritability = `final`** — the class cannot be subclassed, and its members cannot be overridden. ## Opening a class for inheritance To subclass, mark the class `open`. Members are also final by default; mark each overridable member `open` too. The subclass uses `override`. ```kotlin open class Animal { open fun speak() = "..." fun id() = 42 // final: cannot be overridden } class Dog : Animal() { override fun speak() = "Woof" } ``` ## Why final-by-default? This follows *Effective Java* Item 19: "Design and document for inheritance or else prohibit it." Subclassing that the base author didn't anticipate creates the **fragile base class problem** — internal changes silently break subclasses. Making `final` the default forces a deliberate `open` opt-in, so inheritance is always intentional and documented. ## Related keywords - `abstract` classes/members are implicitly `open` (you must override them). - `override` members are themselves `open` again unless you mark them `final override` to stop the chain. - `sealed` is a restricted form of `open` where all subclasses must be known at compile time. - The Gradle `all-open`/`kotlin-spring` compiler plugin auto-opens annotated classes (e.g. Spring `@Component`) so you don't sprinkle `open` everywhere for proxying.

  • How does Kotlin's `internal` differ from Java's package-private?
    `internal` is visible across the whole module (compilation unit), not just one package; Java's package-private is limited to a single package.
  • If a method is `open` in the base and you `override` it, can a further subclass override it again?
    Yes — an `override` member is implicitly `open`. Write `final override` to stop further overriding.

A Kotlin class is like a sealed appliance: you can use it but can't pry it open to modify internals unless the maker added a removable panel (open).

saying these in an interview costs you the question

  • Saying Kotlin classes are open/subclassable by default like Java
  • Claiming the default visibility is package-private
  • Thinking `open` on the class alone makes all members overridable
  • Believing `internal` means package-private
  • Not knowing `abstract` members are implicitly open

context