Kotlin Interoperability with Java

Kotlin and Java work side by side in the same project. You can call Java code from Kotlin and Kotlin code from Java without any special bridges. This makes it easy to adopt Kotlin gradually in existing Java projects or to use the vast Java ecosystem of libraries from Kotlin.

Calling Java from Kotlin

import java.util.Date
import java.util.ArrayList
import java.text.SimpleDateFormat

fun main() {
    // Java standard library works directly in Kotlin
    val date = Date()
    val formatter = SimpleDateFormat("dd-MM-yyyy")
    println(formatter.format(date))

    // Java collections work in Kotlin
    val javaList = ArrayList()
    javaList.add("Kotlin")
    javaList.add("Java")
    javaList.forEach { println(it) }   // Kotlin's forEach on Java list
}

Platform Types


Java methods can return null — Kotlin cannot know if the return is nullable or not.
These are called "platform types" and are written as T! in error messages.

Java code:
  public String getUserName() { return null; }  // might return null!

Kotlin sees:
  getUserName()   returns String!   (could be String or String?)

Best practice: treat Java return values as nullable
  val name: String? = javaObject.getUserName()
  val name = javaObject.getUserName() ?: "Default"

@JvmStatic and @JvmField

// Kotlin companion object → Java static member
class Config {
    companion object {
        @JvmStatic
        fun getVersion(): String = "1.0.0"

        @JvmField
        val MAX_SIZE = 100
    }
}

// Java can call:
// Config.getVersion()   ← works because of @JvmStatic
// Config.MAX_SIZE       ← works because of @JvmField

// Without @JvmStatic:
// Config.Companion.getVersion()   ← verbose in Java

@JvmOverloads — Kotlin Default Args in Java

// Kotlin function with default arguments
// Java does NOT support default arguments
class Greeter {
    @JvmOverloads
    fun greet(name: String = "Guest", greeting: String = "Hello") {
        println("$greeting, $name!")
    }
}

// Without @JvmOverloads — Java must always pass all arguments:
// new Greeter().greet("Alice", "Hi")

// With @JvmOverloads — Java gets overloaded versions:
// new Greeter().greet()              → Hello, Guest!
// new Greeter().greet("Alice")       → Hello, Alice!
// new Greeter().greet("Alice", "Hi") → Hi, Alice!

@JvmName — Control Generated Names

// Kotlin extension functions can conflict with Java method names
@JvmName("isValidEmailAddress")
fun String.isValidEmail(): Boolean = contains("@") && contains(".")

// Java calls it as:
// StringExtensionsKt.isValidEmailAddress(email)

// File-level @JvmName renames the generated class:
@file:JvmName("StringUtils")
package com.myapp.utils

// Java calls functions in this file as:
// StringUtils.someFunction()

Calling Kotlin from Java

// Kotlin file: Calculator.kt
package com.myapp

class Calculator {
    fun add(a: Int, b: Int) = a + b

    companion object {
        @JvmStatic
        fun pi() = 3.14159
    }
}

// Top-level function in MathUtils.kt
fun square(n: Int) = n * n

// Java:
// Calculator calc = new Calculator();
// int result = calc.add(5, 3);
// double pi = Calculator.pi();
// int sq = MathUtilsKt.square(4);   // top-level functions go in FileName + "Kt"

Null Safety at the Boundary

// Annotate Java return types to help Kotlin understand nullability:
// @Nullable  → Kotlin sees String?
// @NonNull   → Kotlin sees String

// In Java:
// @Nullable
// public String findUser(int id) { ... }

// In Kotlin:
// val user: String? = javaService.findUser(42)   // correctly nullable

// Without annotation, Kotlin sees String! (platform type)
// — it compiles but may crash if null at runtime

Using Java Libraries in Kotlin

import com.google.gson.Gson
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit

fun main() {
    // Gson (Java library) in Kotlin
    val gson = Gson()
    val json = gson.toJson(mapOf("name" to "Alice", "age" to 30))
    println(json)   // {"name":"Alice","age":30}

    // OkHttp (Java library) in Kotlin — works perfectly
    val client = OkHttpClient.Builder()
        .connectTimeout(30, TimeUnit.SECONDS)
        .build()
    println("OkHttpClient ready: $client")
}

Practical Interop: Migrating a Java Class

// Original Java class:
// public class OrderService {
//     private String dbUrl;
//     public OrderService(String dbUrl) { this.dbUrl = dbUrl; }
//     public List getOrders(int userId) { ... }
// }

// Kotlin class calling the Java service:
class OrderViewModel(private val service: OrderService) {

    fun loadOrders(userId: Int): List {
        return service.getOrders(userId) ?: emptyList()  // handle platform type
    }
}

// Kotlin replacement for the Java class:
class OrderServiceKt(private val dbUrl: String) {
    fun getOrders(userId: Int): List {
        // Kotlin implementation
        return listOf("Order #1", "Order #2")
    }
}

Leave a Comment

Your email address will not be published. Required fields are marked *