skip to content

Extensions Across Files & Interop

Extensions declared in another file need an import, and from Java they appear as static methods on a facade class you can rename with @file:JvmName. This is what makes Kotlin utility files consumable from Java code.

part ofKotlinoverview, primer and where to startread it →
on this pageshow

questions

5

You declared an extension function in a different file/package than where you want to call it. How do you make it usable, and why is this different from member functions?

level: juniorimportance: must knowfreq 70%

answer

  1. Extension = static function, receiver is first arg
  2. Import by NAME/package, not by receiver type
  3. Same file or same package = no import
  4. Members need no import; extensions do
  5. Use `as` alias to resolve name clashes

basics

~20 s

An extension defined elsewhere is only visible if you import it by name. Add an import line for that function, then call it like normal. Member functions come for free with the object's type, so they need no import.

solid answer

~40 s

Extension functions are resolved by their fully-qualified declaration site, not by the receiver's type. If the extension is in another package, you must `import com.pkg.fileOrTopLevel` — specifically `import com.pkg.myExtension` (the function name) or use a star import. Top-level extensions live on a synthetic file class, but you import the function symbol, not that class. This contrasts with member functions, which are part of the class API and are always visible wherever the type is. IDEs auto-add the import. Wildcard imports (`import com.pkg.*`) pull all top-level declarations including extensions. Note the import is by package + name; the receiver type is irrelevant to whether the import is needed.

code

kotlin · 9 lines
kotlin
// pkg a
package com.a
fun Int.squared() = this * this

// pkg b
package com.b
import com.a.squared        // import the symbol
// import com.a.squared as sq  // or alias on clash
fun demo() = 5.squared()    // 25

go deeper

for a junior

Knows extensions must be imported by name and that the IDE auto-adds the import.

for a middle

Explains extensions are static and resolved by declaration site, plus same-package visibility rules.

for a senior

Discusses wildcard vs aliased imports, name clashes, and overload ambiguity resolution.

for a principal

Frames import-by-symbol as an API-surface/discoverability tradeoff and guides package layout to keep extension visibility predictable across a large codebase.

## What an extension actually is An **extension function** like `fun String.shout() = uppercase() + "!"` is NOT added to the `String` class. The compiler turns it into a plain **static function** that takes the receiver as its first argument. Resolution is **static** and based on the declared (compile-time) type of the receiver. ## Why imports are needed Because the function is just a top-level symbol in some package, the only way the compiler knows it exists at a call site is if that symbol is **in scope**. It is in scope when: - it is declared in the **same file**, or - it is declared in the **same package** (same-package top-level declarations are visible without import), or - you **import** it explicitly. ```kotlin // file: strings/Shout.kt package com.app.strings fun String.shout(): String = uppercase() + "!" ``` ```kotlin // file: Main.kt package com.app import com.app.strings.shout // import by NAME, not by receiver type fun main() { println("hi".shout()) } ``` ## Import forms - **By name:** `import com.app.strings.shout` - **Wildcard:** `import com.app.strings.*` (brings all top-level symbols, including extensions) - **Alias:** `import com.app.strings.shout as bang` — useful when two extensions with the same name clash. ## Contrast with members A **member function** (declared inside the class body) is part of the type's API. Anywhere the type is visible, the member is callable with **no import**. Extensions never modify the class, so they have no such automatic visibility. ## Gotcha: overload ambiguity If two same-named extensions are imported from different packages and both apply, you get an ambiguity error; resolve with an `as` alias or a more specific import.

  • If the extension is in the same package but a different file, do you still need an import?
    No. Top-level declarations in the same package are visible across files without an import.
  • Two imported extensions have the same name and both apply. What happens?
    Overload resolution fails with an ambiguity error; fix it with an `as` alias or by importing only one.

An extension is like a freelancer with a business card: the object can only 'hire' them if you've got their card (the import) on hand; a member is a permanent employee always available.

saying these in an interview costs you the question

  • Says you import the receiver's class to use the extension
  • Claims extensions are added to the class so no import is ever needed
  • Thinks same-package different-file requires an import
  • Confuses extension visibility with the receiver type's visibility

context

open as a page

How does a Java caller invoke a Kotlin extension function like `fun String.shout(): String`, and how are the receiver and any parameters passed?

level: middleimportance: must knowfreq 60%

basics

~10 s

From Java, the extension is a static method on the file's facade class. You pass the receiver as the first argument: instead of 'hi'.shout() you write StringUtilsKt.shout("hi"). Extra parameters follow the receiver.

open as a page

What is the generated 'facade' class for top-level Kotlin functions, and how does @file:JvmName change it?

level: middleimportance: should knowfreq 55%

basics

~10 s

Top-level Kotlin functions and extensions compile into a hidden class named after the file (e.g. Utils.kt becomes UtilsKt). @file:JvmName lets you rename that class so Java code calling it sees a nicer name.

open as a page

When and why would you use @JvmName on an individual extension function (not the whole file), and what interop problem does it solve?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Generic types erase at runtime, so two Kotlin extensions that look distinct can compile to the same JVM signature and clash. Putting @JvmName on one of them gives it a different method name so both can coexist.

open as a page

A teammate marks a top-level extension `internal` and expects to call it from Java, and separately wants several files' extensions under one Java class. Walk through what actually happens and how to do it correctly.

level: seniorimportance: nice to knowfreq 20%

basics

~10 s

Internal extensions get a mangled, hard-to-call name from Java, so don't expect clean access. To group several files' top-level functions under one Java class, give each file the same @file:JvmName plus @file:JvmMultifileClass.

open as a page