Home > Articles

Kotlin Interoperability with Java

Learn the ins and outs of mixing Kotlin and Java, how their language concepts map to each other, what happens under the hood in the Kotlin compiler to aid interoperability, and how you can write code that further facilitates it.

Save 35% off the list price* of the related book or multi-format eBook (EPUB + MOBI + PDF) with discount code ARTICLE.
* See informit.com/terms

This chapter is from the book

This chapter is from the book

Synergy is better than my way or your way. It’s our way.

Stephen Covey

Interoperability has been one of Kotlin’s priorities from its inception. In this chapter, you’ll learn the ins and outs of mixing Kotlin and Java, how their language concepts map to each other, what happens under the hood in the Kotlin compiler to aid interoperability, and how you can write code that further facilitates it.

Using Java Code from Kotlin

Java code can be called from Kotlin in the way you would expect most of the time. In this section, we’ll explore the details and possible pitfalls, as well as how to write Kotlin-friendly Java code and how to improve your Java code to provide additional information for the Kotlin compiler, especially about nullability.

First, it’s important to appreciate the fact that you can easily use the Java standard library and any third-party library in a natural way. This is demonstrated in Listing 5.1.

Listing 5.1 Using Java Libraries from Kotlin

import com.google.common.math.Stats
import java.util.ArrayList

// Using the standard library
val arrayList = ArrayList<String>()  // Uses java.util.ArrayList
arrayList.addAll(arrayOf("Mercury", "Venus", "Jupiter"))
arrayList[2] = arrayList[0]          // Indexed access operator calls ‘get’ and ‘set’

// Looping as usual, ArrayList provides iterator()
for (item in arrayList) { println(item) }

// Using a third-party library (Google Guava)
val stats = Stats.of(4, 8, 15, 16, 23, 42)
println(stats.sum())  // 108.0

Concepts that differ in Java are mapped to Kotlin automatically. For instance, the indexed access operator for collections is translated to get and set calls under the hood. Also, you can still iterate anything that provides an iterator method using a for loop. This is the easiest and most prevalent use case: simply using Java from Kotlin seamlessly without even thinking about it. With this in mind, let’s now explore special cases to be able to handle mixed-language projects confidently.

Calling Getters and Setters

In Kotlin, you don’t explicitly call getSomething or setSomething but instead use the property syntax that calls the getter and setter under the hood. You want to keep it that way when using Java field accessors for consistency and brevity, and it’s possible by default. Listing 5.2 gives an example.

Listing 5.2 Calling Java Getters and Setters

// Java
public class GettersAndSetters {
  private String readOnly = "Only getter defined";
  private String writeOnly = "Only setter defined";
  private String readWrite = "Both defined";

  public String getReadOnly() { return readOnly; }
  public void setWriteOnly(String writeOnly) { this.writeOnly = writeOnly; }
  public String getReadWrite() { return readWrite; }
  public void setReadWrite(String readWrite) { this.readWrite = readWrite; }
}

// Kotlin
private val gs = GettersAndSetters()
println(gs.readOnly)          // Read-only attribute acts like a val property

gs.readWrite = "I have both"  // Read-write attribute acts like a var property
println(gs.readWrite)

gs.setWriteOnly("No getter")  // Write-only properties not supported in Kotlin

This automatic conversion to a Kotlin property works as long as a Java class provides a parameterless method starting with “get” and optionally a single-parameter method starting with “set”. It also works for Boolean expressions where the getter starts with “is” instead of “get”, but there’s currently no such mechanism if they start with “has” or other prefixes.

Write-only properties are currently not supported in Kotlin, which is why setWriteOnly cannot be called with property syntax as writeOnly.

Handling Nullability

Regarding nullability, there’s a gap in expressiveness between Kotlin and Java that has to be handled: in Kotlin, every variable is either nullable or not, in Java there’s no nullability information expressed in the language itself because every variable may potentially be null (except primitive types). However, handling all data coming from Java as nullable in Kotlin would introduce unnecessary code complexity due to null handling. To handle this problem, Kotlin uses so-called platform types for data coming from Java.

Nullables and Mapped Types

As mentioned in Chapter 2, Diving into Kotlin, primitive types are mapped to the corresponding (non-nullable) Kotlin types. For example, int is mapped to kotlin.Int, boolean to kotlin.Boolean, and vice versa. This makes sense because primitive types cannot be null in Java. However, boxed types must be mapped to nullable types, for instance java.lang.Integer to kotlin.Int?, java.lang.Character to kotlin.Char?, and vice versa.

Generally, interoperability with Java introduces the problems of mapping any objects coming from Java to a Kotlin type. Obviously, it’s not possible to map them all to non-nullable types because that would be unsafe. But because you want to exclude nullability from your code as much as possible, Kotlin doesn’t simply make them all nullable either. Kotlin supports nullability annotations from JSR 305,3 JetBrains, Android, FindBugs,4 and more that define annotations such as @Nullable and @NotNull. This additional information allows a better mapping to appropriate Kotlin types.

Platform Types

Ultimately, the Kotlin team decided to leave the nullability decision to the developer in cases where nullability cannot be inferred. This is represented using platform types such as SomeType!. Here, SomeType! means either SomeType or SomeType?. These are the cases where you can choose to store an object of that platform in a nullable or non-nullable variable. See Table 5.1 for several examples of mapped types.

Table 5.1 Mapped Types and Nullability

Java Declaration

Kotlin Type

Explanation

String name;

kotlin.String!

May or may not be null

@NotNull String name;

kotlin.String

Supposed to not be null

@Nullable String name;

kotlin.String?

Can be null

@NotNull String name = null;

kotlin.String

Source of null pointer exception  even when using Kotlin

Java objects without annotation may or may not be null, so Kotlin will map them to the corresponding platform type, allowing you to treat it as either nullable or non-nullable. If annotations are used, Kotlin uses the additional knowledge about nullability. However, objects marked as @NotNull in Java that still contain null by mistake can cause a null pointer exception in Kotlin.

So platform types denote that the associated type may or may not be nullable, and the developer is able to decide whether the platform type should be handled as nullable or not by using the corresponding type; this is shown in Listing 5.3.

Listing 5.3 Handling Platform Types in Kotlin

// Java
public static String hello() { return "I could be null"; }

// Kotlin
val str = hello()                 // Inferred type: String (by default)
println(str.length)              // 15

val nullable: String? = hello()   // Explicit type: String?
println(nullable?.length)        // 15

Without an explicit type, the compiler infers the non-nullable variant of the platform type so that you can use it without handling null. This is unsafe if you don’t think about whether the method you’re calling does actually never return null. Hence, I recommend stating the type explicitly when calling a Java method. That way, your intention and the fact that you thought about nullability are explicit.

You’ll quickly come across more complex platform types as well. These come from mapped types and generic types that may or may not carry nullability information. Consider the examples in Table 5.2.

Table 5.2 Examples of Platform Types (Combined with Mapped Types)

Java Return Type

Platform Type

Explanation

String

String!

May or may not be null, thus either String or String?

List<String>

(Mutable)List<String!>!

Maybe mutable and maybe nullable list, containing maybe nullable Strings

Map<Int, String>

(Mutable)Map<Int!, String!>!

Maybe mutable and maybe nullable map, containing maybe nullable Ints as keys and maybe nullable Strings as values

Object[]

Array<(out) Any!>!

Maybe nullable array, containing maybe nullable Anys or maybe subtypes of it (because arrays are covariant in Java)

long[]

LongArray!

Maybe nullable array of primitive longs (the whole array may be null)

List<? super Integer>

MutableList <in Int!>!

Maybe nullable and mutable list, containing Ints or parent types of it

The first row demonstrates that unannotated types are translated to platform types by default. Next, a List<T> coming from Java may be used as a mutable list or not, and may itself again be nullable or not (like any other type). Additionally, elements of the list are also potentially nullable. The same holds for other generic types and their type arguments, as shown in the third row. There, the generic type arguments Int and String are mapped to their platform type counterparts. This is necessary because Java allows null as both key and value.

Regarding the fourth row, an array coming from Java is also potentially nullable. On top of that, it may contain subtypes of the declared type argument because arrays are covariant in Java so that an Object[] may contain subtypes of Object. This is why it is translated to a potentially out-projected type. Note also that java.lang.Object is translated to kotlin.Any as defined per the mapped types. A complete list of mapped types is available in the language documentation.5 Additionally, arrays of primitive types are mapped to their corresponding specialized Kotlin class, such as IntArray, DoubleArray, and CharArray. Lastly, a contravariant list coming from Java becomes an in-projected type in Kotlin. As you can see, it even becomes a definitely mutable list because it wouldn’t be usable otherwise as an in-projected list. However, you shouldn’t use a wildcard type as a return type in Java because it makes the return type harder to work with for the caller.

Adding Nullability Annotations

Handling platform types is only required if there’s no nullability information the compiler can use. Even though the Java language itself doesn’t have a concept for this, you can actually attach nullability info via annotations such @NotNull and @Nullable. Such annotations are already fairly widespread, and there are various libraries containing such annotations. The Kotlin compiler can currently process the following nullability annotations.

  • The Android annotations @NonNull and @Nullable from the package android.support.annotations. These can be used out of the box on Android.

  • The JetBrains annotations @NotNull and @Nullable from org.jetbrains.annotations that are used for static analysis in Android Studio and IntelliJ IDEA and can also be used out of the box in both.

  • Annotations from the javax.annotation package.

  • FindBugs annotations from the package edu.umd.cs.findbugs.annotations.

  • The Lombok annotation lombok.NonNull.6

  • Eclipse’s7 nullability annotations from org.eclipse.jdt.annotation.

With these, Kotlin supports the most widely used annotations. Listing 5.4 provides an example for how to use JetBrains’ annotations; the others work the same way. If you’re using others, such as NetBeans8 or Spring Framework annotations, you’ll have to transition to one of the above to get better type inference in Kotlin.

Listing 5.4 Using Nullability Annotations

// Java
public static @Nullable String nullable() { return null; }
public static @NotNull String nonNull() { return "Could be null, but with warning"; }

// Kotlin
val s1 = nullable()  // Inferred type: String?
val s2 = nonNull()   // Inferred type: String

Note that nothing stops you from returning null from the nonNull method in Java, you’ll only receive a warning in AndroidStudio and IntelliJ, depending on which nullability annotations you use. For instance, the JetBrains annotations are used for static analysis in Android Studio and IntelliJ so that such code would yield a warning.

In this way, nullability annotations allow you to get more type information in Kotlin. In other words, they disambiguate the impreciseness of Type! to either Type or Type?. Let’s consider the examples in Table 5.3 and contrast them to the above table of platform types.

Table 5.3 Examples of Inferred Types (When Using Nullability Annotations)

Java Return Type

Inferred Kotlin Type

Explanation

@NonNull String

String

Non-nullable String

@Nullable String

String?

Nullable String

@NonNull List <String>

(Mutable)List <String!>

Non-nullable but maybe mutable list, containing maybe nullable Strings

List<@NonNull String>

(Mutable)List <String>!

Maybe nullable and maybe mutable list, containing non-nullable Strings

@NonNull

List<@NonNull String>

(Mutable)List <String>

Non-nullable but maybe mutable list, containing non-nullable Strings

In short, by resolving the ambiguity to either @NonNull or @Nullable, the return type of the Java method is mapped accordingly when called from Kotlin. Note that, to use annotations on generic types argument as in List<@NonNull String>, you need to use an implementation of JSR 3089 such as the checker framework.10 The purpose of JSR 308 is to allow annotating any type in Java, including generic type parameters, and it was incorporated into Java Standard Edition 8.

Escaping Clashing Java Identifiers

When mapping between languages, there is always a disparity between the keywords defined in the languages. Kotlin has several keywords that don’t exist in Java and are thus valid Java identifiers, such as val, var, object, and fun. All these must be escaped using backticks in Kotlin as shown in Listing 5.5. This is done automatically in IntelliJ and Android Studio.

Listing 5.5 Handling Name Clashes

// Java
class KeywordsAsIdentifiers {
    public int val = 100;
    public Object object = new Object();
    public boolean in(List<Integer> list) { return true; }
    public void fun() { System.out.println("This is fun."); }
}

// Kotlin
val kai = KeywordsAsIdentifiers()
kai.`val`
kai.`object`
kai.`in`(listOf(1, 2, 3))
kai.`fun`()

Even though it looks inconvenient, it’s not something you have to think about explicitly thanks to the IDE. It still does clutter up the code a little but should only appear in rare cases anyway. If you have to escape frequently, you may want to rethink your naming standards in Java—none of the above identifiers convey much meaning when used as identifiers.

Calling Variable-Argument Methods

Calling vararg methods defined in Java works naturally so that you can pass in an arbitrary number of arguments. However, in contrast to Java, you cannot pass an array to a vararg method directly. Instead, you have to use the spread operator by prefixing a * to the array. Listing 5.6 shows both ways to call a variable-argument method.

Listing 5.6 Calling a Variable-Argument Method

// Java
public static List<String> myListOf(String... strings) {  // Vararg method from Java
    return Arrays.asList(strings);                      // Passes in vararg unchanged
}

// Kotlin
val list = myListOf("a", "b", "c")                     // Can pass in any number of args
val values = arrayOf("d", "e", "f")
val list2 = myListOf(*values)                          // Spread operator required

Using Operators

You can call Java methods like operators in Kotlin if they have the right signature. For instance, defining a plus or minus method in a class allows you to add or subtract objects of it. Listing 5.7 provides an example.

Listing 5.7 Using Java Methods as Operators

// Java
public class Box {
    private final int value;

    public Box(int value) { this.value = value; }
    public Box plus(Box other)  { return new Box(this.value + other.value); }
    public Box minus(Box other) { return new Box(this.value - other.value); }
    public int getValue() { return value; }
}

// Kotlin
val value1 = Box(19)
val value2 = Box(37)
val value3 = Box(14)

val result = value1 + value2 - value3  // Uses ‘plus’ and ‘minus’ as operators
println(result.value)  // 42

Although you can write Java code that targets the predefined set of operators, you cannot define other methods that allow infix notation in Kotlin.

Using SAM Types

Interfaces with a single abstract method (“SAM types”) can be called from Kotlin without boilerplate thanks to SAM conversions. Listing 5.8 shows how a lambda expression can be used to implement the single abstract method of a SAM interface.

Listing 5.8 Using SAM Types from Kotlin

// Java
interface Producer<T> {  // SAM interface (single abstract method)
  T produce();
}

// Kotlin
private val creator = Producer { Box(9000) }  // Inferred type: Producer<Box>

In the Java code, this defines a SAM type, with produce being the single abstract method. To create a Producer<T> in Kotlin, you don’t need to use an object expression (or an anonymous inner class as in Java) but can instead use a lambda expression that provides the implementation of the single abstract method. In Listing 5.8, you have to add Producer in front of the lambda expression, otherwise the type of the right-hand side would be () -> Box and not Producer<Box>. This is not necessary when passing in a function argument, as shown in Listing 5.9, because the compiler can then infer the type.

Listing 5.9 SAM Conversion in Function Arguments

// Kotlin
val thread = Thread {  // No need to write Thread(Runnable { … })
  println("I'm the runnable")
}

The Thread constructor accepts a Runnable, which is instantiated here using SAM conversions. The code is equivalent to Thread(Runnable { … }). But because the constructor argument is of type Runnable, you’re not required to add an additional type hint in front of the lambda here. Consequently, you can use Kotlin’s convention to move lambdas out of the parentheses and then omit the parentheses because the lambda is the only parameter.

This mechanism is extremely useful for many built-in SAM types such as Comparator, Runnable, and many listener classes.

Further Interoperability Considerations

You’ve now seen that you can call Java code from Kotlin in a natural way most of the time. Sometimes, as with SAM conversions, it’s even more convenient than from Java itself. The main point to keep in mind is nullability and how it maps to Kotlin. Other considerations to keep in mind can be explained briefly.

  • Methods that throw a checked exception in Java can be called from Kotlin without having to handle the exception because Kotlin decided against checked exceptions.

  • You can retrieve the Java class of an object as SomeClass::class.java or instance.javaClass.

  • You can use reflection on Java classes from Kotlin and use a reference to the Java class as the entry point. For instance, Box::class.java.declaredMethods returns the methods declared in the Box class.

  • Inheritance works naturally across Kotlin and Java; both support only one superclass but any number of implemented interfaces.

Since Kotlin was designed with interoperability in mind, it works seamlessly with Java most of the time. The following section explores what you should keep in mind when calling Kotlin code from Java.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020