Start Learning
Javaneer
Back to stage
Stage 0·Why Kotlin, and Interop First

Compiling Alongside Java

Kotlin and Java files coexist in one project, compiled to the same JVM bytecode. The Kotlin compiler, the Gradle plugin, and what a .kt file becomes under the hood.

14 min readIntermediate
On this page

Kotlin's interop isn't a bridge or a wrapper - it's deeper than that: Kotlin compiles to the same JVM bytecode as Java, so a compiled Kotlin class and a compiled Java class are indistinguishable to the JVM. That's why a .kt file and a .java file can live in the same project, in the same build, and call each other with no glue. Understanding what actually happens at compile time makes the rest of this module click.

Same target: JVM bytecode

When you compile a .java file, javac produces a .class file of JVM bytecode. When you compile a .kt file, the Kotlin compiler (kotlinc) produces... the same kind of .class file - JVM bytecode the runtime can't tell apart from Java's:

User.java  ──javac───►  User.class  (JVM bytecode)  ┐
                                                     ├─► same JVM, one classpath
Order.kt   ──kotlinc─►  Order.class (JVM bytecode)  ┘

Because the output is identical in nature, Order.class (from Kotlin) can reference User.class (from Java) and vice versa - they're just classes on the classpath. No serialization, no FFI, no boundary. This is the mechanical truth beneath "100% interop": there is no runtime bridge because there's nothing to bridge.

Mixed-source builds

In practice, a build tool orchestrates this. With Gradle, you apply the Kotlin plugin alongside your Java setup, and put sources in src/main/kotlin and/or src/main/java (Kotlin's plugin lets them mix even in one directory):

// build.gradle.kts (Gradle's Kotlin DSL - itself written in Kotlin)
plugins {
    kotlin("jvm") version "2.0.0"     // the Kotlin JVM plugin
    java                              // Java still works alongside it
}

The Kotlin Gradle plugin handles joint compilation - it understands both languages so a Kotlin class can depend on a Java class and a Java class can depend on a Kotlin class within the same module (the compiler runs in the right order). The result is a single set of .class files in one JAR. Maven works too (the kotlin-maven- plugin), but Kotlin and Gradle are a natural pairing since Gradle's own DSL is Kotlin.

What a .kt file becomes under the hood

A useful mental model: know what Kotlin constructs compile to, because that's exactly what Java sees.

  • A Kotlin class → a normal JVM class. class Order(...) is just a class.
  • A data class → a class with a generated constructor, equals/hashCode/toString, componentN(), and copy() - the boilerplate you'd write by hand in Java, generated into the bytecode.
  • A top-level function (Kotlin allows functions outside any class) → a static method on a synthetic class named after the file. orderUtils.kt with a top-level fun discount() compiles to OrderUtilsKt.discount(), which is exactly how Java must call it.
  • A val property → a private field plus a getter (and a setter for var). Kotlin properties are method-backed, so Java sees getName(), not a public field.

You can see this yourself: IntelliJ's "Show Kotlin Bytecode → Decompile to Java" reveals the Java-equivalent of any Kotlin file - an invaluable tool for understanding interop and debugging surprises.

'Decompile to Java' is your Rosetta Stone

Whenever you're unsure how a Kotlin feature will look to Java - or why some interop call is awkward - use IntelliJ's Tools → Kotlin → Show Kotlin Bytecode, then Decompile. It shows the exact Java the Kotlin compiled to: the generated getters, the static methods for top-level functions, how default arguments and companions are represented. It turns interop from guesswork into something you can just look at.

Two authors writing in one language for one printer

Imagine two authors - one prefers verbose formal prose (Java), the other a terser modern style (Kotlin) - both writing chapters of the same book. It doesn't matter that their drafting styles differ, because both submit their chapters as the same typeset format to the same printing press (JVM bytecode to the JVM). The printer binds them into one book without knowing or caring which author wrote which chapter, and chapter 5 (Kotlin) can reference a character introduced in chapter 2 (Java) freely, because in the final typeset form they're just pages. The Kotlin compiler is a typesetter that renders the terser style into the exact same page format Java uses - which is why the two 'authors' collaborate in one volume with no translation step.

Predict the Java view

A Kotlin file pricing.kt contains a top-level function fun applyTax(amount: Double): Double and a data class Money(val amount: Double, val currency: String). A Java class in the same project needs to call the tax function and read a Money's amount. Predict what the Java-side calls look like, and explain why (referencing what each Kotlin construct compiles to).

Why can Kotlin and Java files coexist and call each other in one project with no glue code?

Key takeaways

  • Kotlin compiles to the same JVM bytecode as Java (.class files), so compiled Kotlin and Java classes are indistinguishable to the JVM - the mechanical basis of 100% interop.
  • There's no runtime bridge: mixed .kt and .java files just reference each other as classes on one classpath.
  • A build tool (Gradle's Kotlin plugin, or Maven's) does joint compilation so classes in either language can depend on each other within a module.
  • Know what Kotlin compiles to: classes → classes, top-level functions → static methods on FileNameKt, val/var → getters/setters, data classes → generated equals/hashCode/toString/copy.
  • IntelliJ's 'Show Kotlin Bytecode → Decompile to Java' reveals the exact Java equivalent of any Kotlin - the go-to tool for understanding interop.
Was this lesson helpful?
Edit this page on GitHub