@JvmStatic & Java Interop

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.

Understanding Java Interoperability

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.

Example: Calling Kotlin Code from Java


// 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.

What is @JvmStatic?

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.

Example: Using @JvmStatic


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.

When to Use @JvmStatic

Example with Properties


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().

@JvmStatic vs @JvmField vs @JvmOverloads

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")

Java Calling Kotlin Companion Object – Without & With @JvmStatic


// 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)
    }
}
  

Key Takeaways

Conclusion

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.