skip to content

How does Java code call a Kotlin top-level function, and what interop pitfalls should you anticipate?

level: middleimportance: should knowfreq 40%

answer

  1. Java: FileNameKt.fn(args) static call
  2. Renaming the .kt file breaks Java callers — pin with @file:JvmName
  3. internal is name-mangled — not Java-callable
  4. @JvmOverloads for default args; suspend/reified don't map cleanly
  5. Extension receiver becomes the first Java parameter

basics

~10 s

Java calls the function as a static method on the generated file class, like FileNameKt.functionName(args). The function name and the 'Kt' suffix matter, and Kotlin-only features may not map cleanly.

solid answer

~30 s

Because a top-level function is emitted as a `public static` method on the `<FileName>Kt` facade, Java invokes it as `FileNameKt.functionName(args)` (importing `com.pkg.FileNameKt` or using the FQN). Pitfalls: (1) renaming or moving the `.kt` file changes the facade class name and **breaks** Java callers; use `@file:JvmName` to pin a stable name. (2) `internal` top-level functions are name-mangled, so they're effectively un-callable from Java. (3) Kotlin-only features need bridges: default arguments require `@JvmOverloads` to generate Java-friendly overloads, and `inline`/`reified` functions and `suspend` functions don't have clean Java signatures. (4) Extension functions appear to Java as static methods whose first parameter is the receiver.

code

kotlin · 10 lines
kotlin
// Kotlin — File: StringUtils.kt
@file:JvmName("StringUtils") // stable Java name
package com.example

@JvmOverloads
fun pad(s: String, width: Int = 8): String = s.padStart(width)

// Java:
//   StringUtils.pad("x");      // uses default width via @JvmOverloads
//   StringUtils.pad("x", 10);

go deeper

for a junior

Can call FileNameKt.fn from Java when told the class name but may not anticipate pitfalls.

for a middle

Knows the static facade call, @JvmOverloads for defaults, and that file renames break callers.

for a senior

Anticipates internal mangling, suspend/reified non-mapping, and pins facade names for stable interop.

for a principal

Designs the Kotlin↔Java boundary deliberately: stable @JvmName facades, overload strategy, and what to keep out of the Java surface.

## The basic call shape A Kotlin top-level `fun shout(s: String)` in `StringUtils.kt` (package `com.example`) is, to Java, a static method on `com.example.StringUtilsKt`: ```java import com.example.StringUtilsKt; String r = StringUtilsKt.shout("hi"); ``` Or fully qualified: `com.example.StringUtilsKt.shout("hi")`. ## Pitfall 1 — fragile facade name The facade name is derived from the **file name**. Renaming `StringUtils.kt` to `Strings.kt` silently changes the Java-visible class from `StringUtilsKt` to `StringsKt`, breaking every Java caller. **Fix:** pin it with a file-level annotation: ```kotlin @file:JvmName("StringUtils") package com.example ``` Now the class is `StringUtils` regardless of the file name. ## Pitfall 2 — internal is mangled An `internal` top-level function is compiled as `public static` but with a **name-mangled** suffix to discourage cross-module Java calls. In practice Java cannot reliably call it. Keep Java-facing functions `public`. ## Pitfall 3 — Kotlin-only features need bridges - **Default arguments:** Java has no defaults, so `fun f(a: Int, b: Int = 0)` exposes only the full-arity method. Add `@JvmOverloads` to generate the overload `f(a)` for Java. - **suspend functions:** carry a hidden `Continuation` parameter and are impractical to call directly from plain Java. - **inline / reified:** inlining happens at Kotlin call sites; Java cannot use `reified` type parameters. - **Vararg / Unit:** `vararg` maps to a Java array param; `Unit`-returning functions map to `void`. ## Pitfall 4 — extension functions A top-level extension `fun String.shout()` becomes a static method whose **first parameter is the receiver**: Java calls `StringUtilsKt.shout("hi")` passing the string explicitly. ## How to inspect Use `javap -p com.example.StringUtilsKt` or IntelliJ's **Show Kotlin Bytecode ▸ Decompile** to see the exact Java-visible signatures before relying on them.

  • Why does a Kotlin default argument not 'just work' from Java?
    Java has no default parameters; the compiler emits only the full-arity method unless you add @JvmOverloads to generate reduced-arity overloads.
  • How would you stop a file rename from breaking Java callers?
    Annotate the file with @file:JvmName("FixedName") so the facade class name is decoupled from the file name.

saying these in an interview costs you the question

  • Calling the function in Java without the 'Kt' (or @JvmName) class qualifier
  • Expecting Kotlin default arguments to appear as Java overloads without @JvmOverloads
  • Assuming internal functions are callable from Java
  • Thinking suspend functions have a plain Java signature
  • Forgetting the extension receiver becomes the first Java parameter

context