skip to content

What exactly is a 'module' for the internal modifier, and when would you choose internal over public or private?

level: seniorimportance: should knowfreq 55%

answer

  1. internal = same module visibility
  2. module = Gradle source set / IntelliJ module / Maven project
  3. Separate subprojects are separate modules
  4. JVM name-mangles internal members
  5. Use internal to hide library internals from consumers

basics

~20 s

internal means visible everywhere within the same compilation module. A module is a set of files compiled together — like one Gradle source set or IntelliJ module. Use internal to expose things across your module but hide them from outside consumers.

solid answer

~50 s

`internal` restricts visibility to the **same module** — the set of Kotlin files compiled together in one step. Concretely a module is: an IntelliJ module, a Gradle **source set** (e.g., `main` is separate from `test`), a Maven project, or one Ant/CLI invocation. Two different Gradle subprojects are different modules, so an `internal` declaration in library A is invisible to consumer B even though both are 'your' code. You choose `internal` when a declaration must be shared **across packages within your module** (which `private` can't do) but must **not** leak into your published API (which `public` would). It's the idiomatic tool for library authors to keep implementation classes hidden behind a clean facade. A subtlety for JVM interop: `internal` members get **name-mangled** in bytecode (e.g., `foo$mymodule`) to discourage Java code from calling them, though it isn't a hard barrier.

code

kotlin · 12 lines
kotlin
// :core module
internal object IdGenerator {       // shared across :core packages
    fun next(): Long = System.nanoTime()
}

public class Repository {
    fun create(): Long = IdGenerator.next()  // OK: same module
}

// :app module (depends on :core)
// IdGenerator.next()  // compile error: internal in another module
fun demo(repo: Repository) = repo.create()  // OK via public API

go deeper

for a junior

Knows internal limits visibility to the module and is broader than private.

for a middle

Can define a module concretely (source set / IntelliJ module) and pick internal vs public for hiding internals.

for a senior

Explains separate-subproject boundaries, main/test friend visibility, and the exposure rule for less-visible types.

for a principal

Designs multi-module API surfaces using internal to keep published contracts small and refactorable; understands JVM mangling tradeoffs and Java interop leakage.

## What `internal` does `internal` makes a declaration visible **everywhere inside the same module** and **nowhere outside it**. It's broader than `private`/`protected` but narrower than `public`. ## What counts as a 'module' A *module* is a set of Kotlin files compiled together as a unit. In practice: - an **IntelliJ IDEA module**; - a **Gradle source set** — importantly, `src/main` and `src/test` are **different** modules, which is *why test code can still see main's `internal` members* (Gradle wires the test source set to depend on main and treats them as one 'friend' module for compilation); - a **Maven project**; - a set of files compiled by a single Ant task / `kotlinc` invocation. Crucially, **separate Gradle subprojects are separate modules**. So: ```kotlin // :library subproject internal class HttpEngine // visible across all packages of :library public class HttpClient(private val engine: HttpEngine = HttpEngine()) // :app subproject (depends on :library) // HttpEngine() // ERROR: internal, not visible across module boundary // HttpClient() // OK: public ``` ## When to choose `internal` | Need | Modifier | |---|---| | Hide within a single file | `private` (top-level) | | Hide within a single class | `private` (member) | | Share across **packages of my module**, but hide from consumers | **`internal`** | | Expose as published API | `public` | The sweet spot for `internal`: a helper/implementation class that several packages in your library use, but that you don't want external code (other Gradle modules) to depend on. It lets you refactor freely without breaking downstream callers — they literally cannot reference it. ## JVM / Java interop subtlety On the JVM, an `internal` member is compiled as **`public` bytecode** but with a **mangled name** (e.g., `someFun$my_module`). This means: - Kotlin enforces `internal` at **compile time**. - **Java** code *can* technically call it (the JVM has no 'internal' concept), but the mangled name makes it awkward and signals 'do not use'. - This is why `internal` is a *language-level* boundary, not a hard runtime guarantee against Java. ## Interaction with other modifiers - You can't make a member *more* visible than its containing type: a `public` function returning an `internal` type is a compile error (the type would leak). - `internal` is commonly combined with `abstract`/`open` inside a module to build internal extension points. ## Recall - module = compiled-together unit (Gradle source set, IntelliJ module, Maven project). - main and test are different modules; test sees main's internal via 'friend' wiring. - Separate subprojects = separate modules. - JVM mangles internal names; Java can still reach them.

  • Why can test code access main's internal declarations?
    Gradle treats the test source set as a 'friend' of main; they're compiled as the same friend-module unit, so internal members of main are visible to tests.
  • Can Java code call a Kotlin internal function?
    Technically yes — internal compiles to public bytecode with a mangled name like foo$module — but it's intentionally awkward and unsupported; Kotlin only enforces internal at compile time.
  • What happens if a public function exposes an internal type?
    Compile error: you can't expose a less-visible type through a more-visible declaration, because callers couldn't reference the returned type.

internal is a staff-only area: any employee (same module) can enter, but customers (other modules) can't — even though there's no locked door on the JVM, just a sign on the name.

saying these in an interview costs you the question

  • Defining a module as a package
  • Saying internal is enforced at runtime against Java
  • Claiming two subprojects share internal visibility
  • Not knowing main and test are separate modules
  • Thinking internal is the same as Java package-private (it's module-wide, not package-wide)

context