Annotations for your Java-friendly Kotlin code
You are here because:
- You have finally decided to try Kotlin.
- You loved it, because, why wouldn’t you?
- You want to use it everywhere.
- You face the sad reality: you can’t not get rid of Java, at least not without some effort.
Why?
If Kotlin is awesome, why can’t I just use it all the time? Two common scenarios come to my mind:
- When you’re trying to slowly migrate your codebase to Kotlin, you’ll notice that sometimes there are some files you just don’t dare Convert Java to Kotlin file. If you have enough time to refactor it, then go for it! In a real project though, that may not always be the case.
- Your code will be consumed by both Java and Kotlin programmers. You can’t (or shouldn’t) force all of them to use an specific language, especially if supporting both will take you little to no effort (using annotations of course).
Here you’ll find a few annotations that will help you Java & Kotlin interoperability!
Java Annotations
JvmField
- What does it do?: Instructs the Kotlin compiler not to generate getters/setters for this property and expose it as a field (source).
- Most common use case: to expose fields from a companion object.
- How does it work?
Let’s say you define a field inside an kotlin±object / companion object
in Kotlin:
object Constants {
val PERMISSIONS = listOf("Internet", "Location")
}
If you try to invoke that function from Java, you would have to write:
Constants.INSTANCE.getPERMISSIONS();
That’s a lot of code to call a simple field! To make the code cleaner, let’s get rid of it by adding the kotlin±@JvmField
annotation.
object Constants {
@JvmField
val PERMISSIONS = listOf("Internet", "Location")
}
Now, our Java code will look like this:
Constants.PERMISSIONS;
We can achieve the same by using the const modifier instead of @JvmField. However, this modifier can only be applied to primitive types or Strings.
//Kotlin
object Constants {
const val KEY = "test"
}
//Java
String key = Constant.KEY;
- When can’t I use it?: properties marked as const, and functions, cannot be annotated with @JvmField .
JvmStatic
- What does it do?: If used on a function, it specifies that an additional static method needs to be generated from this element. If used on a property, additional static getter/setter methods will be generated (source).
- Most common use case: to expose members (function, properties) from a companion object.
- How does it work?
@JvmStatic
is very similar to @JvmField
, as you will see.
Let’s say you define a function inside an object in Kotlin:
object Utils {
fun doSomething() { ... }
}
If you try to invoke that function from Java, you would have to write:
Utils.INSTANCE.doSomething()
We’ll have to access the object INSTANCE
whenever we want to invoke that function. To make the code cleaner, let’s get rid of it by adding the @JvmStatic
annotation.
object Utils {
@JvmStatic
fun doSomething(){ ... }
}
Now, when invoking that function from Java, all we need to do is:
Utils.doSomething();
That’s much better, isn’t it? It’s just as if the function was originally written as a static method in Java.
We can also apply that annotation to fields:
object Utils {
@JvmStatic
var values = listOf("Test 1", "Test 2")
}
So when calling it from Java, we can do:
Utils.getValues();
Notice that @JvmField
exposes the member as a field, but with @JvmStatic
we expose a get function.
And because our field is a var , the set method is also generated:
Utils.setValues(...);
If we have a constant inside our object, we can also declare it as static:
object Utils {
@JvmStatic
val KEY = "test"
}
Yet, in this case, using this annotation wouldn’t be the best idea, because the invocation call would look like this:
public void foo(){
String key = Utils.getKEY();
}
For that scenario, use the const modifier or @JvmField
as explained before.
- When can’t I use it?: a member can’t be annotated with
@JvmStatic
when it has ankotlin±open
,kotlin±override
orkotlin±const
modifier. For instance, this code won’t compile:
JvmOverloads
- What does it do?: Instructs the Kotlin compiler to generate overloads for this function that substitute default parameter values.
- What is an”overload”?: In Kotlin, your function may have default parameters, and that allows you to invoke a same function in multiple ways. To achieve that in Java, you would have to manually define every single variation of that function. Each of that automatically generated variations is an “overload”.
- Most common use case: to overload class constructors. Yet, it can be applied to any kind of function that has default parameters.
- How does it work?
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
)
… you would be able to invoke it from Kotlin in multiple 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 invoke the constructor from Java, you would only have two options: 1) to pass all the parameters, or 2) only in case ALL your parameters have a default value, you could pass no 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, multiple options are available when using Java:
However, here we don’t have as many variations as in Kotlin. For example, there is no way to only pass a last name or age.
The JvmOverloads annotation will only generate as many overloads as default parameters there are in the function.
- When can’t I use it?: There are few limitations for this annotation. As long as you have a function, you can mark it as JvmOverload. You can even combine it with other annotations like JvmStatic.
- When shouldn’t I use it?: This annotation is useless if your function has no default parameters.
@file:JvmName
-
What does it do?: Specifies the name for the Java class or method which is generated from this element. (source)
-
Most common use case: to give a nicer name to a Kotlin file that only have functions. However, it can be applied not only to files but also to functions and property accessors (getters and setters).
-
How does it work?
In Kotlin, where functions are first-class citizens, you can write functions that exist outside of a class. For instance, if you create a new Kotlin file and write this code, it will compile with no problems:
//file name: Utils.kt
fun doSomething() { ... }
You can invoke that code from Java:
UtilsKt.doSomething();
Notice that even though the file name is Utils , the invocation name is UtilsKt, which is not ideal. To fix this, let’s add theJvmName annotation at the top of the file.
//file name: Utils.kt
@file:JvmName("Utils")
fun doSomething() { ... }
Notice that we are using the file: prefix. As you may have guessed, it specifies that the annotation we are using applies at the file-level. If you invoke that code from Java:
Utils.doSomething();
We can also annotate functions:
//file name: Utils.kt
@file:JvmName("Utils")
@JvmName("doSomethingElse")
fun doSomething() { ... }
When calling it from Kotlin, we will still use the original name (doSomething), but in Java we use the name specified on the annotation:
//Java
Utils.doSomethingElse();
//Kotlin
Utils.doSomething()
This may not seem useful, but it can be used to handle signature clashes. That scenario is wonderfully explained in the official documentation.
It also works with properties accessors:
class User {
val likesKotlin: Boolean = true
@JvmName("likesKotlin") get() = field
}
So here is how the Java invocation would look like with and without the annotation:
//Without annotation
new User().getLikesKotlin()
//With annotation
new User().likesKotlin()
The same can be achieved using the get: prefix.
class User {
@get:JvmName("likesKotlin")
val likesKotlin = true
}
-
When can I use it?: You can use it on files, functions, and property accessors, but remember to use the correct prefixes when needed.
-
When shouldn’t I use it?: Randomly assigning an alternate name to a function can cause a lot of confusion. Use it cautiously and keep it coherent.
I hope this guide to Annotations for Java-friendly Kotlin code was helpful for you. The nice thing about Kotlin is that you don’t have to choose only the one language, but can make it accessible to all.