Java Calling Kotlin
If you are embarking on a new Kotlin-focused project, you might think that your only concern is having Kotlin code call Java code in libraries.
However, frequently, Java code needs to call other Java code that we supply:
- Via callbacks
- Via subclasses (e.g., calls to methods that we override)
We need to do those things in Kotlin too, which means that some Java code is going to need to call to Kotlin code that we write.
For example, in Android development, Activity
and AppCompatActivity
are Java classes, but a Kotlin-focused project will create subclasses in Kotlin. As a result:
- You will need to override Java methods like
onCreate()
, creating equivalent Kotlin functions, and Java code in the framework or AndroidX libraries will wind up calling your overridden methods - You may create event listeners, for things like button clicks, which will result in Java classes like
Button
calling your Kotlin listeners
Normally, this all works without much issue. However, from time to time, we do have to worry a bit about how Java calls our Kotlin code.
Public Properties
Kotlin properties look like Java fields. In reality, a Kotlin property is a combination of:
- A getter function
- A setter function (for mutable
var
properties) - A “backing” field that actually stores the data
In Kotlin, we frequently ignore this distinction. In Java, we cannot ignore it, as a public property is not the same as a public Java field. We have no direct access to the field from Java, but we can work with the getter (and, where available, the setter).
So, if we have this Kotlin class:
class KotlinStuff {
val immutableStuff = "foo"
var mutableStuff = "bar"
}
…then we can use the getters for both properties and the setter for mutableStuff
from Java:
public class JavaStuff {
public void useKotlinStuff(KotlinStuff stuff) {
String immutable = stuff.getImmutableStuff();
String mutable = stuff.getMutableStuff();
stuff.setMutableStuff("and now for something completely different");
}
}
Kotlin automatically creates the getter and setter using standard JavaBean naming rules, the same ones that it handles in the Kotlin-calling-Java direction.
By default, these will be get...()
and set...()
, where ...
is replaced by the name of the property, with the initial letter capitalized (immutableStuff
results in getImmutableStuff()
). This is true even for Boolean
properties — Kotlin does not switch to is...()
syntax automatically.
However, if the property name itself begins with is
, then Kotlin will have the getter function use the same name as the property, with a setter function that replaces is
with set
. So, a Kotlin isMutable
property:
class KotlinStuff {
var isMutable = true
}
isMutable()
and setMutable()
Java methods for us to invoke on the Java side: public class JavaStuff {
public void useKotlinStuff(KotlinStuff stuff) {
if (stuff.isMutable()) {
stuff.setMutable(false);
}
}
}
Note that class modifiers like data
or sealed
have no impact on these naming rules, so this KotlinStuff
variant works the same as the previous one from Java’s standpoint:
data class KotlinStuff(var isMutable: Boolean = true)
Other Top-Level Declarations
As we have seen throughout this book, Kotlin supports top-level functions: functions that reside outside of any Kotlin class:
fun doSomething() {
println("something!")
}
Since Java does not have its own top-level function, it needs a little help to call those functions.
From Java’s standpoint, the top-level function is a static
method on a class whose name is:
- the name of the Kotlin source file containing the top-level function…
- …with a
Kt
suffix added to it
If the doSomething()
function shown in the above snippet were in a KotlinStuff.kt
source file, then Java can refer to it as a static
method on KotlinStuffKt
:
public class JavaStuff {
public void doSomethingElse() {
KotlinStuffKt.doSomething();
}
}
Top-level properties are also accessible from Java. However, like class properties, Java will use generated getter and setter methods by default. Those appear as static
methods on the Java class generated from the class name, as with top-level functions.
So, if KotlinStuff.kt
has:
val GLOBAL_VARIABLE = "it's a small world"
…then Java can obtain the value via:
public class JavaStuff {
public void doSomethingGlobal() {
String value = KotlinStuffKt.getGLOBAL_VARIABLE();
}
}
One exception is if const
is used:
const val GLOBAL_VARIABLE = "it's a small world"
Then, the value is accessible in Java as a static
field:
public class JavaStuff {
public void doSomethingGlobal() {
String value = KotlinStuffKt.GLOBAL_VARIABLE;
}
}
This is one advantage of using const val
instead of just val
: you have a more natural way of referencing the constant from Java.
Adjustments via Annotations
Sometimes, you are going to need to have Kotlin change its approach slightly to be more Java-friendly. Through a series of annotations, you can alter how Kotlin code can be used from Java, without interfering with how that same Kotlin code can be used from Kotlin itself.
@file:JvmName()
As we just saw, top-level Kotlin functions can be called as static
methods from Java, using a synthetic Java class based on the Kotlin filename. Sometimes, though, that filename is not particularly convenient. You may have chosen to put that Kotlin code in ASourceFileWithAVeryLongName.kt
, forcing you to call its top-level functions on ASourceFileWithAVeryLongNameKt
. Or, perhaps the filename is short, but it does not make sense as a Java class name. Or, perhaps you just do not like the Kt
suffix on the end.
To control this, put a @file:JvmName()
annotation as the first line of the source file containing the top-level functions that you want to use from Java. You supply the annotation with the name that you want Kotlin to use for the synthetic Java class:
@file:JvmName("CoolStuff")
val GLOBAL_VARIABLE = "it's a small world"
…then Java can obtain the value by using your supplied class name:
public class JavaStuff {
public void doSomethingGlobal() {
String value = CoolStuff.getGLOBAL_VARIABLE();
}
}
@file:JvmMultifileClass
If you use the same @file:JvmName()
annotation (with the same name) in 2+ Kotlin source files, you will get a build error. The assumption is that you made a copy-and-paste error, forgetting to change the name.
However, Kotlin actually supports having more than one Kotlin file contribute to the same Java facade class for things like top-level functions, object
references, and so on. You just have to opt into it by also having @file:JvmMultifileClass
as an annotation, typically immediately after the @file:JvmName()
annotation:
@file:JvmName("CoolStuff")
@file:JvmMultifileClass
// this is in one Kotlin file
val GLOBAL_VARIABLE = "it's a small world"
@file:JvmName("CoolStuff")
@file:JvmMultifileClass
// this is in another Kotlin file
val OTHER_VARIABLE = "...but we can make it bigger!"
Your Java code can reference both CoolStuff.GLOBAL_VARIABLE
and CoolStuff.OTHER_VARIABLE
, even though those two variables are defined in separate Kotlin source files.
@JvmStatic
Whether you use @file:JvmName()
or not, Java can call top-level Kotlin functions.
When it comes to functions on a companion
object, things get a bit more complicated.
For example, suppose that you have a Kotlin class that has a companion object
with some sort of factory function for creating instances:
class KotlinThingy {
companion object {
fun gimme() = KotlinThingy()
}
}
Java can call this by referring to a Companion
synthetic nested class:
public class JavaStuff {
public void doSomethingGlobal() {
KotlinThingy thingy = KotlinThingy.Companion.gimme();
}
}
There is nothing wrong with this. However, if you want, you can add @JvmStatic
to the companion object
function:
class KotlinThingy {
companion object {
@JvmStatic
fun gimme() = KotlinThingy()
}
}
The KotlinThingy.Companion.gimme()
syntax will still work in Java, but now you also can call a gimme()
static
method on the Kotlin class, much like how you use the companion object
functions in Kotlin itself:
public class JavaStuff {
public void doSomethingGlobal() {
KotlinThingy thingy = KotlinThingy.gimme();
}
}
Another place where @JvmStatic
can be useful is with named objects:
object KotlinSingleton {
fun heyNow() {
println("What what?")
}
}
Java can call heyNow()
, but it needs to refer to an INSTANCE
of the object:
public class JavaStuff {
public void doSomethingGlobal() {
KotlinSingleton.INSTANCE.heyNow();
}
}
If, instead, the Kotlin code uses @JvmStatic
:
object KotlinSingleton {
@JvmStatic
fun heyNow() {
println("What what?")
}
}
…then the Java code can skip the INSTANCE
and call heyNow()
directly on the Kotlin object:
public class JavaStuff {
public void doSomethingGlobal() {
KotlinSingleton.heyNow();
}
}
@Throws
Kotlin does not have checked exceptions the way Java does — you are never required by the Kotlin compiler to wrap something in a try
/catch
construct. All JVM-defined exceptions are treated as runtime exceptions by Kotlin. And we have no throws
keyword in Kotlin to advertise that a certain function will throw an exception.
For exceptions that extend Java’s RuntimeException
, this is fine.
We start to run into problems when Kotlin code might throw some other sort of exception (e.g., IOException
) and we want that exception to be caught by Java code calling the Kotlin code. The Java compiler expects all checked exceptions to be reported by a throws
keyword somewhere, and it will refuse to compile code that tries to catch
an exception that is not a RuntimeException
and was not reported by throws
.
So, if we have some Kotlin code that might throw a checked exception:
class KotlinThingy {
fun loadStuff() {
// TODO work that might throw an FileNotException, which we simulate by...
throw FileNotFoundException()
}
}
…and we try to catch
it in Java:
public class JavaStuff {
public void useTheLoadedStuff() {
try {
KotlinThingy thingy = new KotlinThingy();
thingy.loadStuff();
}
catch (FileNotFoundException ex) {
}
}
}
…we get a Java compile error, complaining that loadStuff()
does not throw FileNotFoundException
.
The workaround for this is to annotate the Kotlin function with @Throws
, identifying the exception(s) that the function may throw:
class KotlinThingy {
@Throws(FileNotFoundException::class)
fun loadStuff() {
// TODO work that might throw an FileNotException, which we simulate by...
throw FileNotFoundException()
}
}
This does not change the syntax used by Java to call the function, but it allows the catch
to work.
If the function throws more than one checked exception, supply an array of exception classes to the exceptionClasses
property:
class KotlinThingy {
@Throws(exceptionClasses = [FileNotFoundException::class, CertificateException::class])
fun loadStuff() {
// TODO work that might throw either of those exceptions
}
}
@JvmField
As was noted earlier in the book, a Kotlin property is made up of three pieces:
- A field
- A getter method
- A setter method
Frequently, we ignore the field in Kotlin, as our references to the property automatically use the getter and setter. Similarly, by default, Java cannot access the field — only the Kotlin getter and setter are exposed to Java.
However, there may be rare occasions where you have Java code that needs to access a Kotlin field directly. In those cases, the @JvmField
annotation on the Kotlin property will make the field available, with whatever visibility is defined on the property (public by default, but could be protected
, etc.).
@JvmOverloads
Kotlin supports default parameter values on constructors and functions. Java, through Java 8, does not. By default, what Kotlin exposes to Java will be a function that requires all parameters, meaning that the default values will be ignored.
For example, if you have this Kotlin code:
class KotlinThingy {
fun deeeeeeeeeefault(foo: String, bar: Int = 1, goo: Float = 2.3f) {
// TODO something
}
}
…you might think that this Java could would work:
public class JavaStuff {
public void iCanHazDefaults() {
KotlinThingy thingy = new KotlinThingy();
thingy.deeeeeeeeeefault("non-default");
}
}
However, the Java compiler will complain that you do not have values for the second and third parameters. Even though Kotlin callers do not require them, Java callers do.
There are two exceptions to this rule.
One is with constructors. If your class’ constructor has default values for all constructor parameters, then a secondary zero-parameter constructor is also available for Java. So, if we revise the above Kotlin code to:
class KotlinThingy(parameters: String = "", are: Int = 4, fundamental: Boolean = true) {
fun deeeeeeeeeefault(foo: String, bar: Int = 1, goo: Float = 2.3f) {
// TODO something
}
}
…then the Java code will still be able to create a KotlinThingy
using new KotlinThingy()
, even though the Kotlin constructor uses default parameter values.
The other exception to this rule is if you apply @JvmOverloads
to the constructor or function. This causes the Kotlin compiler to generate additional versions of the constructor or function, progressively applying default parameters and therefore not requiring Java to supply them.
We can apply that to KotlinThingy
both for the constructor and the function:
class KotlinThingy @JvmOverloads constructor(parameters: String, are: Int = 4, fundamental: Boolean = true) {
@JvmOverloads fun deeeeeeeeeefault(foo: String, bar: Int = 1, goo: Float = 2.3f) {
// TODO something
}
}
This version of the constructor only has the latter two parameters defaulted; the first one is required.
In Java, we now have access to three KotlinThingy
constructors:
KotlinThingy(String, int, boolean)
KotlinThingy(String, int)
KotlinThingy(String)
Similarly, we have access to three versions of the function:
deeeeeeeeeefault(String, int, float)
deeeeeeeeeefault(String, int)
deeeeeeeeeefault(String)
As a result, Java code like this will compile cleanly:
public class JavaStuff {
public void iCanHazDefaults() {
KotlinThingy thingy = new KotlinThingy("whatever");
thingy.deeeeeeeeeefault("non-default");
}
}
Prefixes on Other Annotations
Java has its own annotations, and sometimes you will need to apply an annotation defined in Java to some Kotlin code.
Normally, this works fine.
However, sometimes you need to apply an annotation to something specific that Kotlin hides behind other layers.
The biggest culprit here are Kotlin properties. These map to a field, a getter, and a setter in Java. If you need to put the annotation on just one of those things, the annotation alone will not work. Instead, you have to add a prefix to the annotation itself:
-
field:
to apply the annotation to the “backing field” of the property (@field:YourAnnotation
) -
get:
to apply the annotation to the getter (@get:YourAnnotation
) -
set:
to apply the annotation to the setter (@set:YourAnnotation
)
Scenario: JUnit4 Rules
For example, if you use JUnit4 for testing, you might want to use some JUnit4 rules. In Java, you apply the @Rule
annotation to the field that holds the rule:
public class YourTest {
@Rule
public final TemporaryFolder temp = new TemporaryFolder();
// TODO rest of testing
}
Here, we use the stock JUnit 4 TemporaryFolder
rule, to generate a temporary folder suitable for our testing.
The direct equivalent in Kotlin is to put the annotation on a property:
class YourTest {
@Rule
val temp = TemporaryFolder()
// TODO rest of testing
}
However, this confuses the annotation processor. Instead, you can use @get:Rule
to apply the annotation to the getter:
class YourTest {
@get:Rule
val temp = TemporaryFolder()
// TODO rest of testing
}
Scenario: Dagger 2 Qualifiers
You can use custom annotations with Dagger 2 to help qualify particular uses of objects.
For example, you might define a @SpecialCase
custom annotation:
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class SpecialCase
…then use that for some object defined in a module:
@Module
class YourModule {
@Provides
@Singleton
@SpecialCase
fun heyThisIsSpecial() = Whatever()
}
You then might want to @Inject
that @SpecialCase
object as a property somewhere. To make that work successfully, you may need to use @field:SpecialCase
to tell Kotlin to apply the @SpecialCase
annotation to the backing field of the property:
abstract class SomethingOrAnother {
@Inject @field:SpecialCase lateinit var special: Whatever
// TODO rest of this class, including an inject() call on a Dagger component
}
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.