Writing Java-friendly Kotlin code
While Kotlin becomes more and more popular, a lot of Java libraries are getting Kotlin helpers to make their usage from Kotlin code more idiomatic and clean. People, who’ve already tried Kotlin, understand that this language is much more pleasant to write code in than Java. So, wouldn’t it be cool to write libraries in Kotlin in the first place? Using all its niceties, while still keep being Java-friendly on the caller side? This is possible, and in this post I’ll give you some tips on how to achieve it.
One of the main selling sides of Kotlin is its stunning Java interoperability. It’s indeed very easy to call Java code from Kotlin, however, the opposite has its pitfalls. But Kotlin creators have something to our aid.
In this post I’ll talk only in the context of Kotlin’s JVM backend.
Statics, @JvmStatic
Let’s consider an Analytics library we’re writing. We’ve designed the main interaction point as an object:
object Analytics {
fun init() {...}
fun send(event: Event) {...}
fun close() {...}
}
On Kotlin call-site it looks great, simply:
Analytics.send(Event("custom_event"))
But from Java we must do:
Analytics.INSTANCE.send(new Event("custom_event"));
Can we make the Java code look the same as Kotlin’s? Yes, we can. Simply mark all our public methods as @JvmStatic
and they will be compiled as static Java methods:
object Analytics {
@JvmStatic fun init() {...}
@JvmStatic fun send(event: Event) {...}
@JvmStatic fun close() {...}
}...
// Java call-site
Analytics.send(new Event("custom_event"));
If we also have any public
properties in the object
, they should be marked @JvmStatic
as well — while backing fields are static by default, we still need to modify getters and setters:
@JvmStatic var isInited: Boolean = false
private set
Default parameters, @JvmOverloads
At the same time, our Event class looks like:
data class Event(val name: String, val context: Map<String, Any> =
emptyMap())
We set a default value for context because we want to be able to create a marker Event without any data. Getters and setters with proper naming will be conveniently generated for us. And we may expect two constructors to be generated as well: one that takes name
and another that takes name
and context
. Indeed, two constructors will be generated, but not the ones we expect. One will be a full constructor, and the other will be a synthetic full constructor with an additional bit-flag parameter to configure, whether other parameters have non-default explicit values set or not. Luckily, the latter won’t be accessible from Java. Of course, we can use a full constructor everywhere in Java, but can’t we do better? Definitely! We can force generating overloading constructors by annotating the primary constructor with @JvmOverloads
:
data class Event @JvmOverloads constructor(val name: String, val context: Map<String, Any> = emptyMap())
This will create the two constructors that we had originally expected. We also can use this trick for functions with default parameters which may be called from Java.
Checked exceptions, @Throws
At some point of time we decide to add an ability to use plugins with our Analytics.
interface Plugin {
fun init()
/** @throws IOException if sending failed */
fun send(event: Event)
fun close()
}
We design it a way that all plugins are handled in one place, and send
may throw an IOException
, which will be handled in the main library class and logged:
// object Analytics
@JvmStatic fun send(event: Event) {
log("<Internal Send event>")
plugins.forEach {
try {
it.send(event)
} catch (e: IOException) {
log("WARN: ${it.javaClass.simpleName} fired IOE")
}
}
}
There is no need to declare any thrown exception in a Kotlin method signature, and it’s cool. But here comes the Java — one of our library’s consumers decides to write his own plugin in Java, properly throws an IOException
where it’s needed, adds throws
part in the signature and sees a compiler error:
Overridden method does not throw IOException
We don’t want to make our consumers unhappy. Sadly, Java still has checked exceptions, and they must be stated in a method’s signature. For that specific case, Kotlin has @Throws
. Marking our method with this annotation and including all thrown exception classes as its parameters will solve the problem:
interface Plugin {
fun init()
/** @throws IOException if sending failed */
@Throws(IOException::class)
fun send(event: Event)
fun close()
}
Java names, @JvmName
Properties
Now we have plugins and we want to have an ability to check if any are installed. Let’s add a property like this:
val hasPlugins get() = plugins.isNotEmpty()
We know that Kotlin respects Java Bean naming conventions, so any property called name
will turn into getName()
/setName()
methods pair (for var
case). Also we know that if the property is called isName
, it will turn into isName()
/setName()
pair. It’s really great, this helps us on the Java-caller side!
But what about our hasPlugins
property? Obviously, it will turn into getHasPlugins()
method which is ugly. Wouldn’t it be better to call it hasPlugins()
? Again, Kotlin has a solution for this — @JvmName
annotation, and since we need to rename the getter, this annotation should be applied to the getter itself:
val hasPlugins @JvmName("hasPlugins") get() = plugins.isNotEmpty()
or alternatively (by specifying target in the annotation itself):
@get:JvmName("hasPlugins") val hasPlugins get() =
plugins.isNotEmpty()
But this is not the only possible application of @JvmName
.
Functions
Next one — generics erasure problem.
It’s very common for a library to have some Utils classes. Which in Kotlin almost always turns into extension functions. Let’s say our utils have these couple of methods:
fun List<Int>.printReversedSum() {
println(this.foldRight(0) { it, acc -> it + acc })
}
fun List<String>.printReversedSum() {
println(this.foldRight(StringBuilder()) {
it, acc -> acc.append(it)
})
}
In the bytecode both will have the same signature due to erasure:
public static final void printReversedSum(@NotNull List $receiver)
We can fix that using the same annotation:
fun List<Int>.printReversedSum() {
println(this.foldRight(0) { it, acc -> it + acc })
}@JvmName("printReversedConcatenation")
fun List<String>.printReversedSum() {
println(this.foldRight(StringBuilder()) {
it, acc -> acc.append(it)
})
}
NOTE! It is not only needed for Java-callers, but it actually fixes a compiler error. Since all Kotlin code turns into the same JVM bytecode as the Java code itself, you’ll get a collision at the bytecode level, and this code simply won’t compile.
Files
As far as we know, extension functions file will turn into a class with static methods in the bytecode. Therefore, we won’t feel much difference in their usage from Java. Except one — this class will be named for the enclosing file with the suffix Kt
. For example, the following file
//reverser.ktpackage utilfun String.reverse() = StringBuilder(this).reverse().toString()
will turn into ReverserKt
class. Let’s rename it to something more meaningful for Java usage, like ReverserUtils
. Easy:
//reverser.kt
@file:JvmName("ReverserUtils")package utilfun String.reverse() = StringBuilder(this).reverse().toString()
By specifying this familiar annotation before package declaration and with proper annotation target, we make our Java-caller code look better.
@JvmMultifileClass
The last @JvmName
use case often goes along with @JvmMultifileClass
. If we’ll use the same @file:JvmName
in several files of the same package, trying to make their content accessible from the same JVM class, we’ll need @file:JvmMultifileClass
in each of these files. Otherwise, several JVM classes with the same full classname will be generated, which won’t compile. Here is the example from Kotlin Stdlib:
//Collections.kt
@file:kotlin.jvm.JvmMultifileClass
@file:kotlin.jvm.JvmName("CollectionsKt")
package kotlin.collections-----//Iterables.kt
@file:kotlin.jvm.JvmMultifileClass
@file:kotlin.jvm.JvmName("CollectionsKt")
package kotlin.collections
Constants, @JvmField
If our library’s public API includes any constants, we may expose them to Java in several ways:
const
— essentially creates a constant, no accessors. Only works for top-level or object-level primitives andString
s.@JvmField
— makes a backing field accessible on its own, with no accessors. Works withvar
s as well.
For all object
properties with backing fields these fields are generated as static
s on an enclosing class level, which essentially makes them public static
fields.
NOTE! Declaring an
object
property aslateinit
also makes its backing fieldpublic
(while keeping all accessors), which may not be what you want for a public API, so pay attention.
Generics , @JvmWildcard, @JvmSuppressWildcards
We know that Kotlin has declaration-site variance in its toolbox. This means that List<Dog>
can finally be used in place of List<Animal>
without any wildcard magic, no more <? extends …>
or <? super …>
. But in Java we have only use-site variance and Kotlin has to deal with that. So, what happens for List<out E>
?
In the case of the incoming parameters,
fun addPlugins(plugs: List<Plugin>)
will be turned into
public final void addPlugins(@NotNull List<? extends Plugin> plugs)
to let one easily pass collections of Plugin
’s successors. Because it’s the common case for incoming parameters handling in Java. If we want to suppress the use of wildcards, we should declare the method like one of the following:
// Suppress only for this param.
fun addPlugins(plugs: List<@JvmSuppressWildcards Plugin>)// Suppress for the whole method.
@JvmSuppressWildcards fun addPlugins(plugs: List<Plugin>)// Or even for the whole class.
@JvmSuppressWildcards
object Analytics {
fun addPlugins(plugs: List<Plugin>)
}
But in the case of the return value
fun getPlugins(): List<Plugin>
there will be no wildcards
public final List<Plugin> getPlugins()
Because it’s a common case in Java as well, and otherwise one’ll need to handle wildcards by hand.
If we do need wildcards in return values, we can use the following option
fun getPlugins(): List<@JvmWildcard Plugin>
and it will turn into
public final List<? extends Plugin> getPlugins()
For example, if we want to prevent adding new Plugin
s to the returned collection at the compile-time (better also do a defensive copy in that case, because plugins
field is backed by ArrayList
).
NOTE! In case of a contravariant type parameter in with modifier, like in
Comparable<in T>
, wildcards will be generated as<? super …>
.
Working with java.lang.Class
If we’ll also need to handle a bit of reflection in our library, most probably, we will want to support both Class<?>
and KClass<*>
. And there is actually no need to implement these separately — we can convert from one to another quite easily with these built-in extension properties:
val <T> KClass<T>.java: Class<T>
// and
val <T : Any> Class<T>.kotlin: KClass<T>
For example, we’re building a Kotlin version of Retrofit, and we have a method for constructing actual REST API interface implementation which looks like:
fun <T: Any> create(service: KClass<T>): T
Then the corresponding method for Java’s Class<?>
will be the following:
fun <T: Any> create(service: Class<T>): T {
return create(service.kotlin)
}
Builders
Usually, in Kotlin there is no such thing as builders, because we have default and named parameters and apply
function. But for better Java usage, we may consider implementing one. With our simplified Retrofit it may look like this:
class Builder {
private lateinit var baseUrl: String
private var client: Client = Client()
fun baseUrl(baseUrl: String) = this.also { it.baseUrl = baseUrl }
fun client(client: Client) = this.also { it.client = client }
fun build() = Retrofit(baseUrl, client)
}
For required parameters we can use lateinit
modifier, for others — just set the default values. Besides that, to eliminate all the return this
from every setter, we can use my favorite also
or apply
. Pretty concise, isn’t it?
Internal visibility
We also need to pay attention to members with internal
visibility. In bytecode they will turn into public
s, but with long ugly names (not so ugly in the actual library jar, though). It was done, because there is no internal
visibility in Java, and one should not call them unintentionally. Let’s consider our Retrofit example:
// Retrofit.kt
internal fun validate(): Retrofit......// When linking lib as a separate module with sources.
// IntelliJ won't let you compile.final Retrofit retrofit =
new Retrofit.Builder()
.baseUrl("https://api.domain.com")
.client(new Client())
.build()
.validate$production_sources_for_module_library_main();......// When linking lib as a JAR file. Compiles successfully.final Retrofit retrofit =
new Retrofit.Builder()
.baseUrl("https://api.domain.com")
.client(new Client())
.build()
.validate$library_main();
As we can see, one is still able to call them, that’s why we should not use internal
for any essential code that is not supposed to be a public API.
Null-checks
It’s good to know that the bytecode of our Kotlin lib will be augmented with
@Nonnull
and@Nullable
annotations reflecting actual Kotlin types- null-checks for all nonnull parameters and return values in the public API
The former will help our consumers while coding in IDE, the latter will catch incorrect behavior at runtime.
It may happen that you used to use compiler flags
-Xno-param-assertions
and-Xno-call-assertions
to turn off generating runtime null-checks (for example, because you’re 100% Kotlin and you’re paranoid about the performance overhead of these checks on Android). In that case, you should make sure that these flags are not used for the library compilation.
Inline functions
Inline functions will be accessible in Java as usual methods. Of course, they won’t be inlined by the compiler :)
At the same time, inline functions with reified
type parameters won’t be visible in Java at all, because without actual inlining they won’t work.
Unit
If we have functions with the lambdas returning Unit
, we’ll force our Java consumers to deal with it. Because Unit
, similar to Void
, is an actual type, and Java compiler will require such lambda values to have an actual return statement. I know, this sucks:
ReverserUtils.forEachReversed(list, it -> {
System.out.println(it);
return null;
});
Typealiases
It’s simple here: no typealiases are accessible from Java. Not a big deal though, actual types are still there, and Java users are already used to long type names with enclosing generics.
Hopefully, this post will help you to perform more seamless migration to Kotlin in your Java codebases, and also will help to broaden your Kotlin’s usage to writing useful open-source libraries, while not forcing you to abandon all the Java/Groovy/Closure/etc. users.
Don’t forget to check out:
- Kotlin API docs for used annotations
- Kotlin Lang reference section about Interop
- Sample code from this post
If you’re not very comfortable with Kotlin, I suggest to check these also:
- Kotlin Portal. The main url to remember — all you need to learn Kotlin
- Kotlin Lang full reference
- Try Kotlin online. Online IDE with exercises
- Kotlin Koans
- Kotlin in Action. A book by Kotlin team members Dmitry Jemerov and Svetlana Isakova
- Kotlin for Android Developers. A book by Antonio Leiva