How it works
The explanation, with examples you can run.
Calling Java from Kotlin
- Getters and setters become properties.
person.getName()is writtenperson.name, andsetName(x)isperson.name = x.isActive()becomesperson.isActive. - Functional interfaces take lambdas. Any Java interface with a single abstract method (
Runnable,Comparator, a listener) can be passed a Kotlin lambda; this is SAM conversion. - Collections are mapped, not converted. A Java
List<String>appears as(Mutable)List<String!>!: Kotlin doesn't know if you may change it or if it or its elements may be null. - Nullability comes from annotations. Without them you get platform types (
String!). With@Nullable/@NonNullfrom JSpecify, JetBrains, AndroidX and several others, Kotlin uses realString?andStringtypes. JSpecify annotations are enforced as errors by default; most others give warnings unless you configure them as strict. - Checked exceptions are not checked. Kotlin has no
throwsclause and never forces atry.
Calling Kotlin from Java
Java callers see the bytecode shapes, so a few Kotlin features need hints:
// File: Money.kt@file:JvmName("MoneyUtils") // Java sees MoneyUtils, not MoneyKt import java.io.IOException class Rates { companion object { @JvmStatic fun eur(): Rates = Rates() // Rates.eur() in Java, not Rates.Companion.eur() @JvmField val DEFAULT_SCALE = 2 // a public static field, no getter }} @JvmOverloads // generates format(Long) and format(Long, String)fun format(cents: Long, currency: String = "EUR"): String = "%.2f %s".format(cents / 100.0, currency) @Throws(IOException::class) // lets Java catch IOException around the callfun loadRates(path: String): String = java.io.File(path).readText() fun main() { println(format(1999)) // 19.99 EUR println(Rates.DEFAULT_SCALE) // 2}- Properties appear as
getX()/setX(); booleanisXstaysisX(). internaldeclarations are public in bytecode but get a mangled name so Java can't call them by accident.- Default arguments don't exist in Java: use
@JvmOverloads, or Java must pass every argument. - Kotlin lambdas passed to Java are
kotlin.jvm.functions.Function1and so on. For an API meant to be called from Java, declare afun interfaceinstead, so Java can use its own lambdas.
The fun interface
fun interface PriceRule { fun apply(cents: Long): Long } val tenPercentOff = PriceRule { it * 90 / 100 } // SAM conversion for a Kotlin interfaceA fun interface gets SAM conversion in Kotlin and is a normal functional interface for Java.
Mixed projects
Java and Kotlin can live in the same module: the Kotlin compiler runs first (reading the Java sources only to resolve references to them), then javac compiles the Java against Kotlin's output. Converting a file at a time works well, starting with leaf classes and tests. Java annotation processors run through kapt, which generates Java stubs for them to read; processors written for KSP (Room and Moshi, for example) read Kotlin declarations directly, without the stub step.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.