Skip to main content

Kotlin Calling Java

Introduction

One of Kotlin's major strengths is its excellent interoperability with Java. This means that you can leverage existing Java libraries and frameworks while enjoying Kotlin's modern features. Whether you're migrating a project from Java to Kotlin gradually or just want to use some Java libraries in your Kotlin project, understanding how to call Java code from Kotlin is essential.

In this tutorial, we'll explore how Kotlin can call Java code, handle Java types, and work with Java conventions. This seamless integration is possible because both languages compile to Java bytecode and run on the Java Virtual Machine (JVM).

How Kotlin Views Java Code

When you call Java code from Kotlin, the Kotlin compiler automatically translates Java constructs into their Kotlin equivalents. The good news is that most of this happens behind the scenes, so the process feels natural.

Basic Java Method Calls

Calling Java methods from Kotlin is straightforward. Let's look at a simple example:

java
// Java class
public class JavaCalculator {
public int add(int a, int b) {
return a + b;
}

public void printMessage(String message) {
System.out.println(message);
}
}

Now, let's call this Java class from Kotlin:

kotlin
fun main() {
val calculator = JavaCalculator()

// Call the add method
val sum = calculator.add(5, 3)
println("The sum is: $sum") // Output: The sum is: 8

// Call the printMessage method
calculator.printMessage("Hello from Kotlin!")
// Output: Hello from Kotlin!
}

As you can see, calling Java methods from Kotlin looks exactly the same as calling Kotlin methods. There's no special syntax required!

Java Types in Kotlin

While the basic method calls are simple, understanding how Java types are represented in Kotlin is important for seamless interoperability.

Primitive Types

Java primitive types (like int, boolean, char) are mapped to their Kotlin counterparts (Int, Boolean, Char). This happens automatically:

java
// Java class
public class JavaPrimitives {
public int getNumber() {
return 42;
}

public boolean isEnabled() {
return true;
}
}
kotlin
fun main() {
val jp = JavaPrimitives()

val number: Int = jp.number // Java's int becomes Kotlin's Int
val enabled: Boolean = jp.isEnabled() // Java's boolean becomes Kotlin's Boolean

println("Number: $number, Enabled: $enabled")
// Output: Number: 42, Enabled: true
}

Reference Types

Java reference types (like String, Object, etc.) are also mapped to their Kotlin counterparts, but with a key difference: Kotlin adds nullability information.

java
// Java class
public class JavaPerson {
private String name;

public JavaPerson(String name) {
this.name = name;
}

public String getName() {
return name;
}

public String getMiddleName() {
// Could return null
return null;
}
}
kotlin
fun main() {
val person = JavaPerson("John")

// Java String is treated as String! (platform type) in Kotlin
// which means Kotlin doesn't know if it can be null or not
val name = person.name
println("Name length: ${name.length}") // This is fine if name is not null

// For methods that might return null, it's better to be explicit
val middleName: String? = person.middleName
// Safe call is needed as middleName could be null
println("Middle name length: ${middleName?.length ?: "No middle name"}")
// Output: Middle name length: No middle name
}

Platform Types

You may have noticed the String! notation in comments above. This is a "platform type," which means Kotlin doesn't know whether the Java type is nullable or not. The Kotlin compiler allows both nullable and non-nullable operations on platform types, but if you choose wrong, you risk a NullPointerException at runtime.

It's good practice to explicitly declare nullability when working with Java types:

kotlin
// Explicitly declaring nullability
val name: String = person.name // Asserting it's not null
val middleName: String? = person.middleName // Accepting that it might be null

Java Collections in Kotlin

Kotlin has its own collection types, but they're designed to be fully compatible with Java collections.

java
// Java class
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JavaCollections {
public List<String> getNames() {
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
return names;
}

public Map<String, Integer> getAges() {
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 30);
ages.put("Charlie", 35);
return ages;
}
}
kotlin
fun main() {
val jc = JavaCollections()

// Java List becomes Kotlin List
val names: List<String> = jc.names
println("Names: $names")
// Output: Names: [Alice, Bob, Charlie]

// Java Map becomes Kotlin Map
val ages: Map<String, Int> = jc.ages
println("Alice's age: ${ages["Alice"]}")
// Output: Alice's age: 25

// You can use all Kotlin collection functions
val sortedNames = names.sorted()
println("Sorted names: $sortedNames")
// Output: Sorted names: [Alice, Bob, Charlie]

// Finding the oldest person
val oldestPerson = ages.maxByOrNull { it.value }
println("Oldest person: ${oldestPerson?.key} is ${oldestPerson?.value}")
// Output: Oldest person: Charlie is 35
}

Dealing with Java Static Members

Kotlin accesses Java static methods and fields as if they were companion object members:

java
// Java class with static members
public class MathUtils {
public static final double PI = 3.14159;

public static double calculateArea(double radius) {
return PI * radius * radius;
}
}
kotlin
fun main() {
// Access static field
val pi = MathUtils.PI
println("PI value: $pi")
// Output: PI value: 3.14159

// Call static method
val area = MathUtils.calculateArea(5.0)
println("Circle area with radius 5: $area")
// Output: Circle area with radius 5: 78.53975
}

Handling Java Exceptions

Kotlin doesn't have checked exceptions, but it can still catch Java's checked exceptions:

java
// Java class with checked exception
import java.io.IOException;

public class FileReader {
public String readFile(String path) throws IOException {
if (!path.endsWith(".txt")) {
throw new IOException("Not a text file");
}
// In a real app, we would actually read the file here
return "File content would appear here";
}
}
kotlin
import java.io.IOException

fun main() {
val reader = FileReader()

try {
// In Kotlin, you don't need to declare that this might throw an IOException
val content = reader.readFile("example.txt")
println("File content: $content")

// This will throw an IOException
val badContent = reader.readFile("example.pdf")
println("This won't be printed")
} catch (e: IOException) {
println("Error reading file: ${e.message}")
// Output: Error reading file: Not a text file
}
}

SAM Conversions

Java interfaces with a Single Abstract Method (SAM) can be replaced with lambda expressions in Kotlin:

java
// Java interface with a single method
public interface OnClickListener {
void onClick(String view);
}

// Java class using the interface
public class Button {
private String name;

public Button(String name) {
this.name = name;
}

public void setOnClickListener(OnClickListener listener) {
// In a real app, this would be called when the button is clicked
listener.onClick(name);
}
}
kotlin
fun main() {
val button = Button("Submit")

// Java way
button.setOnClickListener(object : OnClickListener {
override fun onClick(view: String) {
println("Button $view clicked!")
}
})

// Kotlin way with SAM conversion
button.setOnClickListener { view ->
println("Button $view clicked again!")
}
// Output:
// Button Submit clicked!
// Button Submit clicked again!
}

Real-World Example: Working with Android SDK

One of the most common scenarios for Kotlin-Java interoperability is Android development. Here's an example of using the Android SDK (which is written in Java) with Kotlin:

kotlin
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

// Find button by ID (Java method)
val loginButton: Button = findViewById(R.id.loginButton)

// Set click listener using SAM conversion
loginButton.setOnClickListener {
// Show toast message (Java method)
Toast.makeText(this, "Login button clicked", Toast.LENGTH_SHORT).show()

// Start login process...
performLogin()
}
}

private fun performLogin() {
// Kotlin implementation of login logic
println("Performing login...")
}
}

In this example, we're:

  1. Extending a Java class (AppCompatActivity)
  2. Overriding Java methods (onCreate)
  3. Calling Java methods (findViewById, setContentView, show)
  4. Using SAM conversion for a Java interface (OnClickListener)

All of this feels natural in Kotlin, demonstrating how seamless the interoperability is.

Advanced Interoperability: Java Generics in Kotlin

Java generics are handled differently than Kotlin generics due to type erasure concerns. Here's how to work with them:

java
// Java class with generics
import java.util.ArrayList;
import java.util.List;

public class Container<T> {
private T item;

public void setItem(T item) {
this.item = item;
}

public T getItem() {
return item;
}

public <R> R convert(Converter<T, R> converter) {
return converter.convert(item);
}

public interface Converter<T, R> {
R convert(T item);
}
}
kotlin
fun main() {
// Using Java generic class in Kotlin
val stringContainer = Container<String>()
stringContainer.item = "Hello, Java!"

val retrievedItem: String = stringContainer.item
println("Container item: $retrievedItem")
// Output: Container item: Hello, Java!

// Using Java generic methods with Kotlin lambdas
val length = stringContainer.convert { it.length }
println("String length: $length")
// Output: String length: 12

// Working with Java's raw types (not recommended but possible)
val rawContainer = Container<Any>()
rawContainer.item = 42

// This requires a cast in Kotlin
val number = rawContainer.item as Int
println("Number: $number")
// Output: Number: 42
}

Summary

Kotlin's interoperability with Java is one of its strongest features, allowing developers to:

  1. Use existing Java libraries and frameworks
  2. Migrate existing projects gradually
  3. Mix and match Java and Kotlin code in the same project

Key points to remember when calling Java from Kotlin:

  1. Most Java code works seamlessly in Kotlin without any special syntax
  2. Java types are mapped to Kotlin types, with nullable and non-nullable variants
  3. Be cautious with platform types (Type!) and consider explicitly declaring nullability
  4. Java collections integrate well with Kotlin's collection functions
  5. SAM interfaces can be replaced with lambdas for cleaner code
  6. Java static members are called like companion object members

With this knowledge, you should be able to confidently integrate Java libraries and code into your Kotlin projects while enjoying the benefits of both languages.

Exercises

  1. Create a simple Java class with methods that return different data types, then call them from Kotlin.
  2. Convert a Java listener interface implementation to a Kotlin lambda expression.
  3. Work with a Java collection in Kotlin, using Kotlin's collection operations.
  4. Handle nullable values coming from Java APIs safely in Kotlin.
  5. Create a project that uses a popular Java library (like OkHttp or Gson) from Kotlin code.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)