Java Interoperability
Calling Java from Kotlin
Section titled “Calling Java from Kotlin”Kotlin code can call any Java class, method, or field without adapters or wrappers. This is a Fundamental design principle of the language.
import java.time.LocalDateTimeimport java.util.Base64
val now = LocalDateTime.now()val encoded = Base64.getEncoder().encodeToString("hello".toByteArray())Getters and Setters
Section titled “Getters and Setters”Java getters and setters are automatically mapped to Kotlin properties.
// Javapublic class User { private String name; private int age;
public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}// Kotlinval user = User()user.name = "Alice" // calls setName()user.age = 30 // calls setAge()println(user.name) // calls getName()Nullability and Platform Types
Section titled “Nullability and Platform Types”Java has no nullability information in its type system. When Kotlin calls Java code, the types are Represented as platform types, written as T!. The compiler neither enforces null safety nor Guarantees non-nullity for platform types.
val list: MutableList<String!> = ArrayList() // platform type// The compiler allows both:list.add(null) // no warningval first: String = list[0] // no warning, could be null at runtimeHandling Platform Types
Section titled “Handling Platform Types”Explicitly declare nullability when calling Java code:
// If you know the Java method never returns null:val result: String = javaObject.toString() // non-null assertion
// If the Java method might return null:val result: String? = javaObject.getProperty("key") // safe@NotNull and @Nullable Annotations
Section titled “@NotNull and @Nullable Annotations”Kotlin recognizes nullability annotations from several frameworks:
- JetBrains:
org.jetbrains.annotations.NotNull/Nullable - Android:
android.support.annotation.NonNull/Nullable - JSR-305:
javax.annotation.Nonnull/CheckForNull - Lombok:
lombok.NonNull
// Java with nullability annotationsimport org.jetbrains.annotations.NotNull;import org.jetbrains.annotations.Nullable;
public class Service { @NotNull public String getName() { return "default"; }
@Nullable public String getNickname() { return null; }}// Kotlin sees:// getName(): String (non-null)// getNickname(): String? (nullable)val name: String = service.getName()val nickname: String? = service.getNickname()Calling Kotlin from Java
Section titled “Calling Kotlin from Java”Top-Level Functions
Section titled “Top-Level Functions”Kotlin top-level functions are compiled to static methods in a class named after the file (with a Kt suffix). Use @JvmName to customize the class name and @JvmField to expose properties as Fields.
package com.example.util
@JvmName("StringUtils")object StringHelpers { fun capitalizeWords(s: String): String = s.split(" ").joinToString(" ") { it.replaceFirstChar { c -> c.uppercase() } }}// JavaString result = StringUtils.capitalizeWords("hello world");Top-Level Functions Without Object Wrapper
Section titled “Top-Level Functions Without Object Wrapper”@file:JvmName("MathUtils")
package com.example.util
fun factorial(n: Int): Long { return if (n <= 1) 1L else n * factorial(n - 1)}// Javalong result = MathUtils.factorial(10);Properties
Section titled “Properties”Kotlin properties are accessed via getters and setters from Java. Use @JvmField to expose the Backing field directly.
class Config { @JvmField val maxRetries = 3
var timeout: Long = 30_000L private set}// JavaConfig config = new Config();int retries = config.maxRetries; // direct field accesslong timeout = config.getTimeout();// config.setTimeout(60_000L); // compile error: setter is privateDefault Parameter Values
Section titled “Default Parameter Values”Kotlin default parameters do not work from Java. Use @JvmOverloads to generate overloaded methods.
class HttpClient( val baseUrl: String, val timeout: Int = 30_000, val retries: Int = 3) { @JvmOverloads fun get(path: String, headers: Map<String, String> = emptyMap()): Response { // ... }}The compiler generates:
// Java (generated overloads)Response get(String path);Response get(String path, Map<String, String> headers);Companion Object Members
Section titled “Companion Object Members”Members in companion objects are accessed via the Companion class from Java. Use @JvmStatic to Generate true static methods.
class Database { companion object { @JvmStatic fun connect(url: String): Connection { // ... }
fun disconnect() { // ... } }}// JavaConnection conn = Database.connect("jdbc:postgresql://localhost/db"); // @JvmStaticDatabase.Companion.disconnect(); // without @JvmStaticSAM Conversions
Section titled “SAM Conversions”SAM (Single Abstract Method) conversions allow a lambda to be converted to a Java functional Interface implementation.
// Java functional interfaceinterface Runnable { void run();}// Kotlin lambda automatically convertedval thread = Thread { println("Running in a thread")}
executor.execute { task -> process(task)}SAM conversion works for Java interfaces with exactly one abstract method. For Kotlin interfaces, Use the fun interface keyword to enable SAM conversion:
fun interface Predicate<T> { fun test(t: T): Boolean}
val isEven = Predicate<Int> { it % 2 == 0 }No-Arg and Named Constructors
Section titled “No-Arg and Named Constructors”@JvmOverloads with Constructors
Section titled “@JvmOverloads with Constructors”class Button @JvmOverloads constructor( val text: String, val color: Int = 0, val fontSize: Int = 14)Generates three constructors accessible from Java: Button(String)``Button(String, int) Button(String, int, int).
@Throws for Checked Exceptions
Section titled “@Throws for Checked Exceptions”Kotlin does not have checked exceptions, but Java callers may need them declared. Use @Throws to Specify which exceptions the function can throw.
@Throws(IOException::class)fun readFile(path: String): String { return File(path).readText()}// Java -- compiler now knows about IOExceptiontry { String content = FileUtils.readFile("/path/to/file");} catch (IOException e) { // handle}Kotlin-Specific Types from Java
Section titled “Kotlin-Specific Types from Java”Unit in Kotlin maps to void when called from Java. A Kotlin function returning Unit is Callable as a void method from Java.
Nothing
Section titled “Nothing”Nothing has no instances. A function returning Nothing never returns normally (always throws).
Kotlin Collections from Java
Section titled “Kotlin Collections from Java”Java code sees Kotlin”s read-only collection types as their mutable equivalents (e.g., List is Seen as java.util.List). This is because Kotlin’s read-only interfaces extend Java’s mutable Interfaces for compatibility.
Common Pitfalls
Section titled “Common Pitfalls”- ** Ignoring platform types. Always annotate Java APIs with nullability annotations when both Java and Kotlin code share the codebase. Unannotated Java APIs create a null safety gap.
- ** Forgetting
@JvmStaticon companion object members. Without it, Java callers must useClassName.Companion.method()Which is awkward. - ** Forgetting
@JvmOverloadsfor constructors and methods with default parameters. Java cannot use Kotlin default parameters. - ** Assuming Kotlin’s read-only collections are immutable when accessed from Java. Java sees
kotlin.collections.Listasjava.util.ListSo it can calladd()``remove()Etc. UseCollections.unmodifiableList()or Kotlinx immutable collections for true immutability. - ** Using Kotlin extension functions from Java. Extension functions compile to static methods, so Java callers must call them explicitly:
ExtensionKt.functionName(receiver, args).
Summary
Section titled “Summary”This topic covers the core concepts of java interoperability, including underlying theory, practical implementation, and key applications.
Key concepts include:
- OOP principles (encapsulation, inheritance, polymorphism)
- collections framework
- streams and lambda expressions
- exception handling
- the JVM and garbage collection
Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Intuition
Section titled “Intuition”Java-Kotlin interoperability is like two countries sharing a border — most traffic flows freely, but there are customs checkpoints where the rules differ. Platform types are the border guards: they represent values where Kotlin cannot enforce null safety because the information was lost on the Java side. SAM conversions let Kotlin lambdas slip through as Java functional interfaces, bridging the syntactic gap. The @Jvm annotations are like passport stamps that make Kotlin code behave in ways Java expects. Understanding interoperability means understanding where the two languages’ type systems diverge and where the bridges are.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.