Calling Kotlin from Java

Annotations for Java Callers

AnnotationEffect for Java code
@JvmStatica companion/object function becomes a real static method: Config.create() instead of Config.Companion.create()
@JvmOverloadsgenerates overloads for parameters with default values
@JvmFieldexposes a property as a public field (no getter)
const valcompile-time constant, a static final field
@file:JvmName("Utils")top-level functions in this file go to class Utils instead of FileNameKt
@JvmName("...")rename a function/getter in bytecode (solves signature clashes)
@Throws(IOException::class)adds throws so Java callers can catch a checked exception

Checking the Bytecode with Reflection

Example

We can ask the JVM what Java code will see.

import java.lang.reflect.Modifier

class Config {
    companion object {
        @JvmStatic fun create() = Config()
        fun plain() = Config()
        const val VERSION = "2.1"
        @JvmField val TAG = "cfg"
    }
}

class Greeter @JvmOverloads constructor(
    val name: String,
    val greeting: String = "Hello",
    val punctuation: String = "!",
)

fun main() {
    val methods = Config::class.java.declaredMethods.map { it.name }
    println("Config has static create(): ${"create" in methods && Modifier.isStatic(Config::class.java.getMethod("create").modifiers)}")
    println("Config has plain() directly: ${"plain" in methods}")

    val fields = Config::class.java.declaredFields.filter { Modifier.isStatic(it.modifiers) }.map { it.name }.sorted()
    println("Static fields: $fields")

    val ctorSizes = Greeter::class.java.constructors.filter { !it.isSynthetic }.map { it.parameterCount }.sorted()
    println("Greeter constructors for Java: $ctorSizes parameters")
}

Output

Config has static create(): true
Config has plain() directly: false
Static fields: [Companion, TAG, VERSION]
Greeter constructors for Java: [1, 2, 3] parameters

The Java Side

// Java code using the Kotlin classes above
Config c = Config.create();            // works thanks to @JvmStatic
Config d = Config.Companion.plain();   // without it, go through Companion
String v = Config.VERSION;             // const val
String t = Config.TAG;                 // @JvmField

Greeter g1 = new Greeter("Asha");                  // @JvmOverloads
Greeter g2 = new Greeter("Asha", "Namaste");
Greeter g3 = new Greeter("Asha", "Namaste", "!!");

// Kotlin: @Throws(IOException::class) fun load(path: String): String
try {
    String text = FileLoader.load("data.txt");
} catch (IOException e) {
    e.printStackTrace();
}