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:

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:

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:

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
}
…results in 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:

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:

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:

Similarly, we have access to three versions of the function:

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:

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.