skip to content

How Kotlin Interops With Java

Kotlin and Java compile to the same bytecode and share the same class model, so interop is mostly about how each side's types and members are projected onto the other. The @Jvm family of annotations is how you tune that projection when the default is awkward.

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

questions

5

Why can Kotlin and Java call each other so seamlessly? Explain the underlying model that makes interop possible.

level: juniorimportance: must knowfreq 70%

answer

  1. Same JVM bytecode / .class files
  2. Kotlin reuses Java's class model, not its own
  3. String IS java.lang.String (mapped type)
  4. Properties -> getter/setter, object -> INSTANCE
  5. Top-level fn -> static on FileNameKt

basics

~20 s

Both Kotlin and Java compile into the same kind of code the JVM runs (bytecode). A Kotlin class becomes a normal JVM class, so Java sees it as just another class, and vice versa. No bridge or wrapper is needed.

solid answer

~40 s

Kotlin and Java are both JVM languages: the Kotlin compiler (kotlinc) emits standard JVM .class files (bytecode) that are indistinguishable in shape from those javac produces. So a Kotlin class is a real JVM class with real fields, methods, and a constructor; Java code resolves and links against it through normal classpath resolution. Kotlin reuses Java's class model rather than inventing its own runtime: Kotlin's String IS java.lang.String, its collections are the java.util ones, and a Kotlin object becomes a class with a static INSTANCE field. Kotlin-only concepts that have no JVM equivalent (nullability, default arguments, properties) are encoded via emitted accessors, synthetic methods, and metadata annotations the Kotlin compiler reads. This shared bytecode foundation is why interop is bidirectional and mostly zero-overhead.

code

kotlin · 14 lines
kotlin
// Kotlin source
package demo
class User(val email: String)
fun build() = User("[email protected]")

// Decompiled shape (conceptual):
// public final class User {
//   private final String email;
//   public final String getEmail() { return email; }
//   public User(String email) { this.email = email; }
// }
// public final class DemoKt {
//   public static final User build() { return new User("[email protected]"); }
// }

go deeper

for a junior

Knows both compile to JVM bytecode and that a Kotlin class is just a normal class Java can use.

for a middle

Can explain mapped types (String, collections) and how properties/objects/top-level fns are emitted.

for a senior

Articulates the @kotlin.Metadata role, primitive vs boxed mapping, and that there's no translation layer.

for a principal

Frames interop as a design constraint shaping API surfaces and library boundaries across mixed-language modules.

## The core idea **Bytecode** is the low-level instruction format the **JVM (Java Virtual Machine)** executes. Java's compiler `javac` turns `.java` files into `.class` files containing bytecode. Kotlin's compiler `kotlinc` does the **same thing**: it produces `.class` files of bytecode that the JVM loads exactly like Java's. Because the *output format is identical*, the JVM cannot tell whether a class originally came from Kotlin or Java — they are all just classes on the **classpath** (the set of compiled classes available at runtime). ## Why this enables interop Since a Kotlin class compiles to an ordinary JVM class with ordinary methods and fields, Java code can `import` it and call it through normal **classpath resolution** (the JVM linking a call to the target class/method). The reverse is also true: Kotlin reuses Java's existing class library instead of shipping its own. - `kotlin.String` is literally `java.lang.String` at runtime — it is a **mapped type**, not a wrapper. - Kotlin's `List`, `Map`, `MutableList` are backed by `java.util` collections. - Kotlin numbers (`Int`, `Long`) map to JVM primitives (`int`, `long`) where possible, boxing to `Integer`/`Long` only when needed (e.g. used as a generic type argument or nullable). ## Encoding Kotlin-only features Kotlin has concepts the JVM has no slot for. The compiler encodes them so Java still sees usable members: - A **property** (`val name: String`) becomes a private field plus a `getName()` getter (and `setName()` for `var`). - An **`object` declaration** (singleton) becomes a class with a `public static final INSTANCE` field. - **Top-level functions** in `Foo.kt` become `static` methods on a generated `FooKt` class. - **Nullability**, **default arguments**, and other Kotlin metadata are stored in a `@kotlin.Metadata` annotation that `kotlinc` reads back when compiling Kotlin-against-Kotlin (Java ignores it). ```kotlin // Greeter.kt class Greeter(val name: String) { // -> field + getName() fun greet() = "Hi, $name" } object Config { val version = "1.0" } // -> Config.INSTANCE.getVersion() fun helper() = 42 // -> GreeterKt.helper() (static) ``` ```java // From Java Greeter g = new Greeter("Ann"); String s = g.greet(); // normal method call String v = Config.INSTANCE.getVersion(); int n = GreeterKt.helper(); // top-level fn as static ``` ## Takeaway Interop is **not** a translation layer or FFI. It is the same runtime, the same class model, the same `.class` files — Kotlin just layers extra source-level features on top and encodes them into standard bytecode shapes that Java can still consume.

  • Where does a top-level Kotlin function end up so Java can call it?
    As a static method on a synthetic class named after the file, e.g. functions in Demo.kt become DemoKt.someFunction(); rename with @JvmName.
  • Does using a Kotlin List from Java give you java.util.List?
    Yes. Kotlin collection types are mapped to java.util types at runtime, so Java sees a normal java.util.List.

Two authors writing in different styles but the same alphabet — the printer (JVM) reads the same letters regardless of who wrote them.

saying these in an interview costs you the question

  • Claiming Kotlin runs on its own VM or needs a runtime bridge to Java
  • Saying Kotlin String is a wrapper around java.lang.String rather than being it
  • Thinking interop uses reflection or FFI under the hood
  • Believing Java must be 'converted' to Kotlin to be called

context

open as a page

What is a platform type in Kotlin, why does it exist, and what risk does it introduce?

level: middleimportance: must knowfreq 68%

basics

~20 s

When Kotlin calls Java, Java values have no nullability info, so Kotlin can't tell if they can be null. It treats them as 'platform types' and trusts you. If you assume non-null but it's actually null, you get an NPE.

open as a page

What do @JvmStatic, @JvmField, @JvmName, @JvmOverloads, and @JvmMultifileClass do, and when would you reach for each?

level: middleimportance: should knowfreq 60%

basics

~20 s

These @Jvm* annotations change the Java-facing shape of compiled Kotlin so Java can call it naturally — making members static, exposing plain fields, renaming, generating overloads for default arguments, or merging top-level functions into one class.

open as a page

Explain how Kotlin maps types to the JVM: mapped types, primitives vs boxing, and what happens to Kotlin-only types like Unit and Nothing.

level: seniorimportance: should knowfreq 50%

basics

~20 s

Many Kotlin types are just renamed JVM types (Kotlin String is Java String). Numbers use fast primitives when possible and box only when they must be nullable or generic. Unit becomes void-like; some types like Nothing have no real runtime value.

open as a page

When mixing Kotlin and Java, how do SAM conversions, Kotlin properties, and the read-only/mutable collection split behave across the boundary?

level: seniorimportance: nice to knowfreq 38%

basics

~20 s

A Kotlin lambda can stand in for a Java single-method interface (SAM conversion). Kotlin properties show up as getX/setX to Java. And Kotlin's read-only vs mutable list distinction disappears at runtime, so Java can still mutate a 'read-only' list.

open as a page