Java Interop
Where the two languages meet — platform types from unannotated Java, @JvmStatic, @JvmOverloads and @JvmName, @Throws, how Kotlin declarations map to bytecode, calling a suspend function from Java, and the places 100% interop actually leaks.
12 questions
JuniorTheoryVery commonA Kotlin function has default arguments. What does Java see, and what does @JvmOverloads do?
A Kotlin function has default arguments. What does Java see, and what does @JvmOverloads do?
Java has no default arguments, so the function compiles to one method with every parameter — Java must pass them all. @JvmOverloads makes the compiler generate the overload chain, one per dropped trailing parameter, so Java can omit them.
Common mistakes
- ✗Assuming Java can omit a Kotlin default argument without
@JvmOverloads - ✗Thinking
@JvmOverloadschanges anything for Kotlin call sites - ✗Expecting an overload per parameter combination rather than per trailing parameter
Follow-up questions
- →Why does
@JvmOverloadsgenerate only trailing-parameter overloads, not every combination? - →What do you add to a constructor with default arguments so Java can use its short form?
JuniorTheoryVery commonWhat is a platform type T!, and why can Kotlin still throw an NPE on a value of that type?
What is a platform type T!, and why can Kotlin still throw an NPE on a value of that type?
A value from unannotated Java has unknown nullability, so the compiler gives it platform type T! and lets you use it as T or T?; it enforces nothing. Assign it to a non-null T and the inserted check throws when the value is null.
Common mistakes
- ✗Believing Kotlin's null safety still holds for values returned by unannotated Java
- ✗Reading
T!as a synonym forT?and expecting the compiler to force a null check - ✗Blaming the NPE on Kotlin rather than on the missing
@Nullableon the Java side
Follow-up questions
- →Which Java annotations make Kotlin map a return value to
String?instead ofString!? - →Where exactly does the compiler insert the null check for a platform type?
JuniorTheoryCommonHow does Java call a member of a Kotlin companion object, and what does @JvmStatic change?
How does Java call a member of a Kotlin companion object, and what does @JvmStatic change?
A companion object is a real singleton, not a static section, so Java goes through it: Foo.Companion.bar(). @JvmStatic makes the compiler also emit a static method on Foo, so Java writes Foo.bar(); the companion call still works.
Common mistakes
- ✗Believing a
companion objectcompiles to Javastaticmembers - ✗Calling
Foo.bar()from Java without@JvmStaticand hitting a compile error - ✗Thinking
@JvmStaticremoves theFoo.Companion.bar()call form
Follow-up questions
- →What does
@JvmFieldon a companion property change for a Java caller? - →Where else may
@JvmStaticbe placed besides acompanion objectmember?
JuniorTheoryCommonHow does a Kotlin val or var property appear to the Java code that uses it?
How does a Kotlin val or var property appear to the Java code that uses it?
A property compiles to a private backing field plus accessors: val name gives Java getName(), and a var adds setName(); Java calls the methods, never the field. A Boolean named isDone keeps that name: isDone() and setDone().
Common mistakes
- ✗Expecting Java to access a Kotlin property as a public field
- ✗Forgetting that a
BooleannamedisDonekeeps the getterisDone(), notgetIsDone() - ✗Thinking a
valstill gives Java a setter
Follow-up questions
- →What does
@JvmFieldon a Kotlin property change for a Java caller? - →How do you rename only the getter that Java sees, without renaming the property?
JuniorTheoryOccasionalWhy is calling Java from Kotlin nearly free, while calling Kotlin from Java takes extra work?
Why is calling Java from Kotlin nearly free, while calling Kotlin from Java takes extra work?
Kotlin reads Java declarations directly, so that direction is nearly free; the catch is platform types, with unknown nullability. The other way hits what Java lacks: no default arguments, companion object members are not static.
Common mistakes
- ✗Assuming Kotlin's null safety protects values that arrive from unannotated Java
- ✗Expecting Java to call a Kotlin function with default arguments by omitting them
- ✗Thinking a
companion objectmember is compiled to a static method
Follow-up questions
- →Which annotation makes a
companion objectmember callable as a static from Java? - →What does Java see when a Kotlin function declares default argument values?
MiddleTheoryOccasionalWhat does @JvmName change for a Java caller, and which clash does it classically fix?
What does @JvmName change for a Java caller, and which clash does it classically fix?
@JvmName renames only what Java sees: a function, accessor, or file class. Its classic job is an erasure clash — two functions differing only in a generic argument get one JVM signature, so one must be renamed. Kotlin keeps the old name.
Common mistakes
- ✗Thinking
@JvmNamealso renames the declaration for Kotlin call sites - ✗Expecting two functions with the same erased signature to compile without a rename
- ✗Forgetting that the file class Java sees is
FooKtunless@file:JvmNamerenames it
Follow-up questions
- →Why do two Kotlin functions differing only in a generic argument collide on the JVM?
- →Where must
@file:JvmNamebe placed, and what exactly does it rename?
MiddleTheoryOccasionalAn unannotated Java library gives you platform types. How do you restore real nullability?
An unannotated Java library gives you platform types. How do you restore real nullability?
Annotate the Java side with nullability annotations Kotlin honours (JSpecify, JetBrains) and T! becomes a real T or T?. If it is not your library, wrap it in a thin Kotlin boundary of honest types, checking once, not !! everywhere.
Common mistakes
- ✗Believing
!!at every call site is a fix rather than a relocation of the crash - ✗Thinking Kotlin ignores nullability annotations placed on Java declarations
- ✗Expecting the compiler to null-check a platform type on its own
Follow-up questions
- →Which nullability annotation families does the Kotlin compiler actually honour?
- →What should the Kotlin wrapper do when the Java value really is
null?
MiddleTheoryOccasionalHow does Java reach a member of a Kotlin object Foo, and how do you shorten that call?
How does Java reach a member of a Kotlin object Foo, and how do you shorten that call?
An object compiles to a class holding its one instance in a static INSTANCE field, so Java calls Foo.INSTANCE.bar(). Mark the member @JvmStatic and the compiler adds a static forwarder, so Java writes Foo.bar(); INSTANCE stays.
Common mistakes
- ✗Expecting
Foo.bar()to work from Java for a Kotlinobjectwithout@JvmStatic - ✗Confusing the
INSTANCEfield of anobjectwith theCompanionfield of a class - ✗Thinking
@JvmStaticremoves theINSTANCE-based call form
Follow-up questions
- →When is the singleton behind a Kotlin
objectactually initialised? - →What does
@JvmFieldon a property of anobjectexpose to Java?
MiddleTheoryOccasionalHow does a suspend fun look to Java, and how do you expose it to Java callers?
How does a suspend fun look to Java, and how do you expose it to Java callers?
A suspend fun compiles to a method with an extra trailing Continuation, returning Object — the result or a suspended marker. Java cannot build one, so wrap it in a CoroutineScope and give Java a CompletableFuture or callback.
Common mistakes
- ✗Assuming Java can call a
suspend funby passingnullas theContinuation - ✗Expecting a
suspend funto hand Java aCompletableFutureautomatically - ✗Thinking
@JvmStaticor@JvmNamemakes asuspend funusable from Java
Follow-up questions
- →What does a
suspend funactually return at the moment the coroutine suspends? - →Which coroutine builder gives Java a
CompletableFuturefor asuspend fun?
MiddleTheoryOccasionalWhy can Java not catch a checked exception thrown by Kotlin code, and how do you fix it?
Why can Java not catch a checked exception thrown by Kotlin code, and how do you fix it?
Kotlin has no checked exceptions and emits no throws clause, so javac sees a method declaring nothing and rejects catch (IOException e). @Throws(IOException::class) on the declaration writes the clause into the signature.
Common mistakes
- ✗Assuming Kotlin emits a
throwsclause because the function really can throw - ✗Believing
@Throwschanges anything for Kotlin callers - ✗Writing the
try/catchin Java without adding@Throwson the Kotlin side
Follow-up questions
- →Does
@Throwschange how a Kotlin caller has to handle that exception? - →What does
@Throwsemit for asuspend funseen from Java?
MiddleTheoryRareCan Java call a Kotlin inline fun <reified T>? Justify it from how the function is compiled.
Can Java call a Kotlin inline fun <reified T>? Justify it from how the function is compiled.
Java cannot call it at all. reified exists only because the function is inline: the compiler substitutes the real T at each Kotlin call site, so no method with a usable T is left. Give Java a non-inline overload taking Class<T>.
Common mistakes
- ✗Assuming
reifiedsurvives compilation as a hiddenClassparameter Java could pass - ✗Believing
@JvmNameor@JvmStaticcan expose areifiedfunction to Java - ✗Forgetting that
reifiedworks only because the body is inlined at Kotlin call sites
Follow-up questions
- →What signature would you give the non-inline overload that Java is meant to call?
- →Why must a
reifiedtype parameter always sit on aninlinefunction?
SeniorTheoryRareKotlin claims 100% interoperability with Java. Where does that claim actually leak?
Kotlin claims 100% interoperability with Java. Where does that claim actually leak?
Kotlin calls Java seamlessly, bar platform types erasing null safety. The reverse leaks: default arguments need @JvmOverloads, a reified inline fun is uncallable, suspend fun gains a Continuation, and internal is public, mangled.
Common mistakes
- ✗Reading full interoperability as a symmetric guarantee in both directions
- ✗Assuming
internalhides a declaration from Java - ✗Expecting Java to omit a default argument or to call a
reifiedinline function
Follow-up questions
- →How does an extension function appear in the signature that Java sees?
- →What stops Java from handling a Kotlin
sealedhierarchy exhaustively?