Java Interoperability - Calling Kotlin from Java
When Kotlin was first developed, it was designed to run on the Java Virtual Machine (JVM). As a result, Kotlin offers seamless interoperability with Java. This means we can easily call Kotlin code from Java, just like we do with Java classes and methods. However, there are some specific rules and behaviors to keep in mind when accessing Kotlin features from Java. In this article, we will understand the key points of using Kotlin code in Java.
Kotlin Properties
In Kotlin, a property such as
var age: Int
is compiled into Java as a private field along with corresponding getter and setter methods. For the above property, the generated Java code would look like as shown below.
private int age;
public int getAge() { ... }
public void setAge(int value) { ... }
We can access the property in Java using standard method calls as show below.
Person person = new Person();
int age = person.getAge();
person.setAge(25);
If a Kotlin property starts with the keyword is, for example:
var isActive: Boolean
then the getter in Java will be named isActive() instead of getIsActive().
Package-level Functions
In Kotlin, functions can be declared at the top level (outside any class). When compiled, these functions become static methods inside a generated Java class. The name of the generated class is based on the Kotlin file name.
Example: firstProgram.kt
package kotlinPrograms
fun add(a: Int, b: Int): Int = a + b
This function can be invoked in Java using the following syntax:
int result = kotlinPrograms.FirstProgramKt.add(3, 5);
Changing the Generated Class Name
We can change the name of the generated Java class using @JvmName annotation.
@file:JvmName("Sample")
package kotlinPrograms
fun add(a: Int, b: Int): Int = a + b
This function can be invoked in Java using the following syntax:
int result = kotlinPrograms.Sample.add(3, 5);
Handling Multiple Files with the Same Name
If we have multiple Kotlin files with the same name, it may lead to name conflicts. To combine them into a single Java class, we use the @JvmMultifileClass annotation in each file:
Example - First Kotlin File:
@file:JvmMultifileClass
@file:JvmName("Sample")
package sample.example
fun print() = println("Hello")
Example - Second Kotlin File:
@file:JvmMultifileClass
@file:JvmName("Sample")
package sample.example
fun printString() = println("World")
Both these functions can be invoked in Java using the following syntax:
sample.example.Sample.print();
sample.example.Sample.printString();
Static fields
In Kotlin, if we want a property to behave like a static field in Java, we declare it inside a companion object or object, and use the @JvmField annotation, or the const modifier.
Example:
class Program {
companion object {
@JvmField val x = 10
const val y = 20
}
}
This function can be invoked in Java using the following syntax:
int val1 = Program.x;
int val2 = ProgramKt.y;
Static methods
The methods defined at the package level are always generated as static methods in the Java file. Also the methods defined in named objects and companion objects if annotated with @JvmStatic annotation are generated as static methods. This annotation declares the following function to be a class function.
Example:
class Example {
companion object {
@JvmStatic fun add() = println("Add")
fun sub() = println("Subtract")
}
}
This function can be invoked in Java using the following syntax:
Example.add(); // Works: static method
Example.sub(); // Error: not static
Example.Companion.sub(); // Works: accessed via Companion object
This same behavior applies to Kotlin object declarations as well.
Instance Fields
Kotlin provides a feature to use a property as an instance field in Java. To do this annotate the property with @JvmField annotation. These instance fields have the same visibility as the Kotlin property. However, the property must have a backing field and must not be declared with private, open, const and override modifiers.
Example
class ABC(val value: Int) {
@JvmField var id: Int = 0
}
This property now can be accessed in Java as
ABC obj = new ABC(5);
System.out.println(obj.id);
Checked Exceptions
All exceptions in Kotlin are unchecked. This means Kotlin functions do not declare checked exceptions in their signatures. If we want Java code to handle Kotlin exceptions properly, we need to use the @Throws annotation.
Example:
@Throws(IOException::class)
fun print() {
throw IOException("IO error")
}
This function now can be accessed in Java as:
try {
Sample.Program.print();
} catch (IOException e) {
// Handle exception
}
Without the @Throws annotation, the Java compiler will not recognize that the function might throw a checked exception, and it will result in a compilation error.