Today we will touch upon a crucial topic: the interoperability of Java and Kotlin . The authors of the proposed publication reasonably assume that it is not possible to rewrite the code base made in Java to Kotlin. Therefore, it is more correct to ensure the interaction of Java and Kotlin code. Read how to do this with annotations.
I think you opened this article for the following reasons:
- Finally decided to try Kotlin.
- You liked him, which, however, is not surprising.
- Decided to use Kotlin everywhere
- Faced with harsh reality: Java cannot be completely abandoned, at least with little blood.
Why?
If Kotlin is so cool, why not use it everywhere and always? Here, offhand, a couple of scenarios in which this is not possible:
- When you try to slowly transfer your entire code base to Kotlin, you will notice that there are files that are simply scared to use the
Convert Java to Kotlin file
command. If you have time for refactoring, do it! However, in a real project, time for refactoring is not always found. - Your code will be used by programmers working with both Java and Kotlin. You cannot (or should not) force them all to use a specific language, especially if supporting both languages does not require much effort from you (naturally, I'm talking about annotations).
Here we look at a few annotations that provide interoperability between Java and Kotlin!
Java Annotations
Jvmfield
- What she does? Tells the Kotlin compiler not to generate getters and setters for this property and provide it as a field.
- The most common practical case is to provide companion object fields.
How it works?
Suppose you define a field inside an
object / companion object
in Kotlin:
object Constants { val PERMISSIONS = listOf("Internet", "Location") }
If you try to call this function from Java, you have to write:
Utils.INSTANCE.getPERMISSIONS()
A lot of code for a simple field! To make the code cleaner, let's remove the excess by adding an annotation.
object Constants { @JvmField val PERMISSIONS = listOf("Internet", "Location") }
Now our Java code will look like this:
Utils.PERMISSIONS;
The same can be achieved with a modifier, however, such a modifier only works with primitive types or strings.
//Kotin object Constants { const val KEY = "test" }
//Java String key = Constant.KEY;
When can this annotation not be used?
const
properties marked as and functions cannot be annotated with
@JvmField
Jvmstatic
- What she does? If it is used with a function, it indicates that an additional static method should be generated from this element. If it is used with a property, additional static getter methods and setter methods will be generated.
- The most common practical case: providing members (functions, properties) from a companion object.
How it works?
Suppose you define a function in
object
in Kotlin:
object Utils { fun doSomething(){ ... } }
If you try to call this function from Java, you will have to write:
Utils.INSTANCE.doSomething()
We have to access the
INSTANCE
object whenever we want to call this function. To make the code cleaner, let's better use the
@JvmStatic
annotation.
object Utils { @JvmStatic fun doSomething(){ ... } }
Now, calling this function from Java, we will only have to write:
Utils.doSomething();
So much better, isn't it? The situation is as if the function was originally written in Java as a static method.
Annotations can also be applied to fields:
object Utils { @JvmStatic var values = listOf("Test 1", "Test 2") }
Calling this code from Java, you can write:
Utils.getValues();
Note:
JvmField
provides a member as a field, but with
JvmStatic
we provide a
get
function.
And since the field is
var
, the
set
method is also generated:
Utils.setValues(...);
If we have a constant inside our object, then we can also declare it as static:
object Utils { @JvmStatic val KEY = "test" }
However, in this case, using annotation is not a good idea, since invocation invocation would look like this:
public void foo(){ String key = Utils.getKEY(); }
In this case, use the mod modifier or JvmField, as explained above.
When can’t it be used?
A member cannot be annotated with JvmStatic when it is followed by an
open
,
override
or
const
modifier.
In this situation, the code does not compile:

JvmOverloads
- What she does? Tells the Kotlin compiler to generate overloads for this function, which replaces the default parameter values.
- What is overload? In Kotlin, your function may have default parameters, so you can call the same function in different ways. To achieve the same in Java, you would have to manually define each individual variation of this function. Each of these automatically generated variations is called “overload”. The most common use case: overloading class constructors. Yes, this technique works with any function that has default settings.
How it works?
If you have a class with a constructor (or any other function) with default parameters ...
class User constructor ( val name: String = "Test", val lastName: String = "Testy", val age: Int = 0 )
... then you can call such a function from Kotlin in various ways:
val user1 = User() val user2 = User(name = "Bruno") val user3 = User(name = "Bruno", lastName = "Aybar") val user4 = User(name = "Bruno", lastName = "Aybar", age = 21) val user5 = User(lastName = "Aybar") val user6 = User(lastName = "Aybar", age = 21) val user7 = User(age = 21) val user8 = User(age = 21, name = "Bruno") ...
However, if you try to call the constructor from Java, you will have only two options: 1) pass all parameters or 2) only if ALL of your parameters have default values, you can not pass any parameters at all.
If we want to create overloads, we can use the
JvmOverloads
annotation:
class User @JvmOverloads constructor ( val name: String = "Test", val lastName: String = "Testy", val age: Int = 0 )
Now when using Java, we have many opportunities:

However, in Kotlin there are not many options in this case. For example, we will not be able to pass only the last name or only age.
Annotation
JvmOverloads
only generate as many overloads as the default parameter function has.
- If you have a function, you can mark it as
JvmOverload
. You can even combine it with other annotations, for example, withJvmStatic
. - When should you not use it? This annotation is useless if the function does not have default parameters.
file : JvmName
- What she does? Specifies a name for the Java class or method generated from this element.
- The most common case: give a more beautiful name to the Kotlin file. However, this annotation is applicable not only with files, but also with functions, as well as with methods for accessing properties (getters and setters).
How does she work?
In Kotlin, where functions are privileged elements, you can write functions that exist outside the class. For example, if you create a new Kotlin file and write the following code, then it compiles without problems:
//file name: Utils.kt fun doSomething() { ... }
You can call this code from Java:
UtilsKt.doSomething();
Please note: although the file is called Utils, the call uses the name
UtilsKt
, which is not ideal. To fix this, let's add a
JvmName
annotation on top of the file.
// : Utils.kt @file:JvmName("Utils") fun doSomething() { ... }
Notice how the
file:
prefix is used. You probably guessed it: it indicates that the annotation we are using is applied at the file level. If you call the following code from Java:
Utils.doSomething();
You can also annotate functions:
// : Utils.kt @file:JvmName("Utils") @JvmName("doSomethingElse") fun doSomething() { ... }
When calling this code from Kotlin, we will still use the original name (
doSomething
), but in Java we use the name specified in the annotation:
//Java Utils.doSomethingElse(); //Kotlin Utils.doSomething()
This feature does not seem particularly useful, however, it can be used to resolve signature conflicts. This script is well understood in the official documentation .
Here you can work with methods for accessing properties:
class User { val likesKotlin: Boolean = true @JvmName("likesKotlin") get() = field }
See how this call will look in Java with and without annotation:
// new User().getLikesKotlin() // new User().likesKotlin()
The same can be achieved with the
get
prefix.
class User { @get:JvmName("likesKotlin") val likesKotlin = true }
- When can I use this opportunity? With files, functions, methods for accessing properties. However, be sure to put the necessary prefixes if necessary.
- When should you not use it? If you arbitrarily set the function an alternate name, you can create a lot of confusion. Use this annotation carefully, and if you apply, then use it consistently.
I hope you find this annotation overview useful, helping you write code in Kotlin that is easy to use with Java.