Writing Java-friendly Kotlin code

Sergey Ryabov
AndroidPub
Published in
10 min readJul 24, 2017

--

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 and Strings.
  • @JvmField — makes a backing field accessible on its own, with no accessors. Works with vars as well.

For all object properties with backing fields these fields are generated as statics on an enclosing class level, which essentially makes them public static fields.

NOTE! Declaring an object property as lateinit also makes its backing field public (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 Plugins 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 publics, 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:

If you’re not very comfortable with Kotlin, I suggest to check these also:

This article was initially prepared for SDJournal. I’d like to thank its team for proofreading the article.

Thank you for reading and please tap the ❤ button if you liked it. You can also follow me on Twitter. Happy coding!

--

--