Java aus Kotlin aufrufen
Kotlin nutzt deine bestehenden Java-Bibliotheken ohne Klebecode - und 'Platform Types' sind die Art, wie es an der Grenze mit Javas fehlender Nullability-Information umgeht.
Deutsche Übersetzung in Arbeit
Diese Lektion ist noch nicht ins Deutsche übersetzt und wird daher auf Englisch angezeigt. Der Rest der Seite ist vollständig lokalisiert.
Auf dieser Seite
Calling Java from Kotlin is the easy direction - it "just works." Every Java library you know - Spring, Jackson, JUnit, Apache Commons, your own code - is usable from Kotlin with no wrappers, and often reads better in Kotlin than in Java. There's exactly one wrinkle worth understanding deeply: platform types, Kotlin's careful handling of the fact that Java doesn't tell it which values can be null. Master that one concept and Java interop holds no surprises.
It just works
Point Kotlin at a Java class and use it directly - construct it, call its methods, read its properties:
import java.util.ArrayList // a Java class
import java.time.LocalDate // Java's date API
fun main() {
val list = ArrayList<String>() // Java class, Kotlin syntax
list.add("hello")
val today = LocalDate.now() // static Java method
println(today.plusDays(7)) // fluent Java API, Kotlin call site
}Better still, Kotlin often makes Java APIs nicer. A Java getter getName() can be read as a Kotlin property
obj.name; a Java method taking a Runnable or other single-method interface can be called with a Kotlin
lambda (executor.submit { doWork() }); Java arrays, generics, and varargs all map across. Your Java
knowledge transfers wholesale - you're just writing the call sites in Kotlin.
The one wrinkle: platform types
Here's the subtlety. Kotlin's headline feature is null safety: a String can never be null, a String? can.
But Java has no such distinction - a Java method returning String might return null, and (usually) the bytecode
doesn't say. So when Kotlin calls Java, what type is that returned String?
Kotlin's answer is the platform type, written String! (you'll see it in IDE hints, though you don't type the
!). A platform type means: "Kotlin doesn't know if this is nullable - it trusts you." Kotlin relaxes its null
checks for platform types, letting you treat the value as either nullable or non-null:
val name: String = javaObject.getName() // you ASSERT it's non-null (NPE at runtime if wrong)
val name2: String? = javaObject.getName() // you treat it as nullable (safe)This is a deliberate pragmatic compromise: if Kotlin forced every Java return to be nullable, calling Java would be a misery of null checks; if it assumed non-null, you'd get no safety. Platform types put the judgment in your hands at the boundary.
Handling the boundary safely
Because a platform type can secretly be null, the boundary between Java and Kotlin is where interop NPEs hide. Good practice:
- Decide nullability explicitly at the boundary. When you take a value from Java, assign it to a
String(you assert non-null) orString?(you handle null) - don't leave it implicit and forget. - Respect Java's nullability annotations. If the Java code is annotated (
@Nullable,@NotNullfrom JetBrains, JSR-305, or JSpecify), Kotlin reads them and treats the value as properly nullable or non-null - no platform type, full safety. This is why well-annotated Java libraries (like modern Spring) interop beautifully. - Be defensive with unannotated Java returning values you'll dereference - a wrong non-null assertion crashes exactly where Kotlin's null safety was supposed to protect you.
Platform types are where Kotlin's null safety has a gap - mind the seam
Inside pure Kotlin, NPEs are essentially designed out. The one place they sneak back in is the Java boundary: a platform type you assert as non-null but which returns null at runtime throws an NPE - the very thing you switched to Kotlin to avoid. So treat every Java call that returns a reference as a decision point: is this nullable? Modern, nullability-annotated Java (Spring, many libraries) closes the gap automatically; for unannotated legacy Java, you carry the responsibility. The seam is small, but it's the one place to stay alert.
Java values arriving in Kotlin are like parcels crossing a border where the sender's customs form left the
'may contain nothing (null)' box blank. Kotlin, at the border, doesn't refuse the parcel (that would make trade
impossible) nor blindly assume it's full - it hands it to you as a platform type and says 'I can't verify what's
inside; you decide how to treat it.' If you declare 'this is definitely full' (String) and it turns out empty
(null), you get a nasty surprise when you open it (an NPE). If the sender did fill in the customs box - a
@Nullable/@NotNull annotation - the border can verify it and hands you a properly labeled parcel with full
guarantees. The lesson: trust the parcels from senders who label honestly (annotated Java), and open the unlabeled
ones (unannotated Java) with a little care.
You call a legacy Java method String findEmail(Long userId) from Kotlin. It's unannotated, and you know from its
implementation that it returns null when no email is on file. A colleague writes val email: String = service.findEmail(id) and dereferences it. What's the risk, and how should the boundary be handled - both if you
can and can't modify the Java?
What is a 'platform type' in Kotlin, and why does it exist?
Key takeaways
- Calling Java from Kotlin just works: every Java library is usable with no wrappers, and often reads nicer (Java getters as Kotlin properties, SAM interfaces as lambdas).
- The one wrinkle is platform types: Java gives Kotlin no nullability info, so a Java-returned reference is a 'platform type' (String!) Kotlin trusts you to handle.
- A platform type lets you treat the value as non-null (you assert it) or nullable (you handle it) - a pragmatic compromise so calling Java isn't a null-check misery.
- The Java boundary is where interop NPEs hide: a wrong non-null assertion on a value that's actually null throws - decide nullability explicitly at the seam.
- Kotlin reads Java nullability annotations (@Nullable/@NotNull, JSpecify), turning platform types into fully-checked types - which is why well-annotated Java (modern Spring) interops perfectly.