Calling Kotlin from Java
The other direction, which needs a little care: @JvmStatic, @JvmOverloads, and @JvmName control how Kotlin's features (default args, companions, top-level functions) appear to Java.
On this page
The reverse direction - calling Kotlin from Java - also works, but it's the direction that needs a little
care. Kotlin has features Java doesn't (default arguments, top-level functions, companion objects, properties),
and when Java calls Kotlin, those features have to be expressed in terms Java understands. A handful of
@Jvm... annotations let you control exactly how your Kotlin appears to Java callers - essential when you're
writing Kotlin that a Java codebase must consume.
The mismatch: Kotlin has things Java lacks
Java sees Kotlin as ordinary bytecode (last lesson), but some Kotlin conveniences translate awkwardly by default:
- Default arguments -
fun greet(name: String = "world")is one function in Kotlin, but Java has no default parameters, so by default Java can only call it with all arguments supplied. - Top-level functions - Java can't call a function outside a class, so Kotlin puts it on a
FileNameKtclass as a static method (which Java must reference by that generated name). - Companion objects - Kotlin's replacement for
staticmembers lives in a nestedCompanionobject, so Java seesMyClass.Companion.create()rather thanMyClass.create(). - Properties - a Kotlin
val/varis a getter/setter to Java, not a field.
These aren't broken - they just look clunky from Java unless you nudge them. That's what the annotations do.
The @Jvm annotations
Four annotations cover almost every "make this nice for Java" need:
@JvmStatic- on a companion-object member, generates a real static method so Java callsMyClass.create()instead ofMyClass.Companion.create():
class InvoiceFactory {
companion object {
@JvmStatic fun create(id: String) = Invoice(id) // Java: InvoiceFactory.create("A")
}
}@JvmOverloads- on a function with default arguments, generates overloaded methods for each combination, so Java can call it with fewer arguments:
@JvmOverloads
fun greet(name: String = "world", loud: Boolean = false) { ... }
// Java gets: greet(name, loud), greet(name), and greet()@JvmName- renames the generated class or method Java sees. Useful to give a top-level-functions file a clean class name (@file:JvmName("Pricing")so Java callsPricing.applyTax()notPricingKt.applyTax()), or to resolve a signature clash.@JvmField- exposes a Kotlin property as a plain public field (no getter) when Java code expects direct field access.
Reach for these when - and only when - your Kotlin is meant to be consumed by Java.
Design for the consumer
The practical rule: if Kotlin code has Java callers, design its public API with them in mind. A pure-Kotlin module with no Java consumers needs none of these annotations - the interop machinery is invisible within Kotlin. But a Kotlin library or shared module used by Java should:
- Add
@JvmStatic/@JvmOverloadsto factory methods and functions with defaults, so Java calls read naturally. - Use
@JvmName/@file:JvmNameto give top-level function files a clean class name. - Consider whether Java callers need the null-safety guarantees carried across (Kotlin's non-null types emit runtime null checks that protect Java callers too).
This mirrors the API-design principle from the Architecture path: your public surface is a contract, and here the consumer's language shapes what a good contract looks like.
Only annotate the Java-facing surface
Don't sprinkle @JvmStatic/@JvmOverloads everywhere reflexively - they add generated methods and only matter at the Kotlin→Java boundary. Apply them to the public API that Java actually calls, and leave internal Kotlin code clean. A quick check: if a Kotlin member has no Java callers, it needs no @Jvm annotations. When in doubt, write a small Java call site (or check the decompiled bytecode) to see whether the default interop is already ergonomic.
Pure Kotlin talking to Kotlin is a household speaking its native language - no translation needed. But when Java guests visit and need to use your kitchen (call your Kotlin API), some of your labels confuse them: your one jar marked 'greet (name optional)' means nothing to a guest who can only read fixed labels (no default arguments), and your communal 'statics shelf' is tucked inside a cupboard called 'Companion' they don't think to open. The @Jvm annotations are you adding clear second-language labels for the guests: @JvmOverloads puts out several pre-filled jars so a guest can grab 'greet()' or 'greet(name)'; @JvmStatic moves the shared items onto an obvious shelf labeled the way guests expect; @JvmName renames a cupboard sensibly. You only relabel the things guests actually touch - the rest of your kitchen stays in your own convenient language.
You wrote a Kotlin utility that a large Java codebase must use: a top-level function fun formatMoney(amount: Double, currency: String = "USD", symbol: Boolean = true): String in a file moneyUtils.kt, and a factory in a
companion object: fun of(cents: Long): Money. Currently Java calls are ugly. Show which annotations make each
Java-friendly and what the resulting Java calls look like.
Why do you sometimes need @JvmStatic, @JvmOverloads, or @JvmName when calling Kotlin from Java?
Key takeaways
- Calling Kotlin from Java works but needs care, because Kotlin has features Java lacks: default arguments, top-level functions, companion objects, and properties.
- @JvmStatic generates a real static from a companion member (Java: MyClass.create() not MyClass.Companion.create()).
- @JvmOverloads generates overloaded methods for a function with default arguments, so Java can call it with fewer args.
- @JvmName renames the generated class/method Java sees (e.g. give a top-level-functions file a clean class name); @JvmField exposes a property as a plain field.
- Design the Java-facing surface with the consumer in mind - annotate only the API Java actually calls, and leave pure-Kotlin code clean.