Kotlin is designed to be fully interoperable with Java. This means that Kotlin code can call Java code,
and Java code can call Kotlin code seamlessly — both can coexist within the same project.
However, because Kotlin doesn’t use the static keyword like Java, some adjustments are needed
to make Kotlin classes behave predictably when used from Java.
That’s where @JvmStatic and other JVM annotations come into play.
When Kotlin code is compiled, it runs on the Java Virtual Machine (JVM). Therefore, every Kotlin class, function, and property is compiled into bytecode that Java can understand. This makes Kotlin and Java mutually compatible in mixed-language projects.
// Kotlin file: Utils.kt
class Utils {
companion object {
fun greet(name: String) = "Hello, $name!"
}
}
// Java file: Main.java
public class Main {
public static void main(String[] args) {
// Java way of calling companion object function
System.out.println(Utils.Companion.greet("Talha"));
}
}
Notice how in Java, we need to call Utils.Companion.greet() because Java
doesn’t automatically recognize Kotlin’s companion object members as static methods.
This is where @JvmStatic becomes useful.
The @JvmStatic annotation tells the Kotlin compiler to generate a
static method in the compiled bytecode, making it accessible directly
from Java without needing to reference Companion or the enclosing object.
class Utils {
companion object {
@JvmStatic
fun greet(name: String) = "Hello, $name!"
}
}
// Java file: Main.java
public class Main {
public static void main(String[] args) {
// Now can be accessed directly — simpler!
System.out.println(Utils.greet("Talha"));
}
}
Thanks to @JvmStatic, the function behaves like a static method from the Java side,
even though Kotlin internally doesn’t use the static keyword.
static methods.
class Config {
companion object {
@JvmStatic
val version = "1.0"
@JvmStatic
fun showVersion() = println("App Version: $version")
}
}
// Java code
public class Main {
public static void main(String[] args) {
System.out.println(Config.getVersion());
Config.showVersion();
}
}
Without @JvmStatic, Java would need to access these through
Config.Companion.getVersion() or Config.Companion.showVersion().
| Annotation | Purpose | Usage Example |
|---|---|---|
@JvmStatic |
Makes a function or property static in bytecode for Java access. | @JvmStatic fun greet() |
@JvmField |
Exposes a Kotlin property as a public Java field (no getters/setters). | @JvmField val id = 10 |
@JvmOverloads |
Generates overloaded methods for functions with default parameters. | @JvmOverloads fun show(msg: String = "Hi") |
// Without @JvmStatic
class Helper {
companion object {
fun sayHi() = "Hi from Kotlin!"
}
}
// With @JvmStatic
class StaticHelper {
companion object {
@JvmStatic
fun sayHi() = "Hi from Static Kotlin!"
}
}
// Java code
public class Main {
public static void main(String[] args) {
System.out.println(Helper.Companion.sayHi()); // Needs Companion
System.out.println(StaticHelper.sayHi()); // Direct call (cleaner)
}
}
@JvmStatic when you want Java to call Kotlin methods like traditional static ones.@JvmField and @JvmOverloads improve Java compatibility further.The @JvmStatic annotation bridges the small gap between Kotlin’s modern object-oriented design and Java’s traditional static model. It ensures that Kotlin classes remain easy to use from Java without sacrificing Kotlin’s expressive and safe design. Understanding these interop features is essential when maintaining or migrating mixed Kotlin-Java codebases.