Java vs Kotlin: Which Should You Choose on the JVM?
Kotlin is a modern language that runs on the JVM and interoperates with Java 100%. It fixes most of Java's historical pain points β verbosity, null checks, lack of real functional constructs β while staying fully compatible with existing Java code. Google made it the preferred language for Android in 2019.
At a glance
| Java | Kotlin | |
|---|---|---|
| First release | 1996 | 2011 (1.0 in 2016) |
| Null safety | Runtime only (NullPointerException) | Compile-time β types are non-nullable by default |
| Data classes | record since Java 16 | data class since day one, more features (copy, destructuring) |
| Coroutines | Virtual threads (Java 21+) | Coroutines β fine-grained suspend / async / Flow |
| Extension functions | No | Yes β add methods to existing types without subclassing |
| Interop with Java | Itself | 100% bidirectional β call Java from Kotlin and vice versa |
| Android status | Still supported | Google-preferred since 2019 |
Syntax comparison
The classic "data carrier" class:
// Java, pre-16
public class User {
private final String name;
private final int age;
public User(String name, int age) { this.name = name; this.age = age; }
public String getName() { return name; }
public int getAge() { return age; }
// equals, hashCode, toStringβ¦
}
// Java 16+
public record User(String name, int age) {}
// Kotlin
data class User(val name: String, val age: Int)
Null handling:
// Java β any reference can be null, compiler doesn't help
String name = user.getName();
if (name != null) System.out.println(name.length());
// Kotlin β type system separates nullable from non-nullable
val name: String? = user.name // nullable
val fixed: String = user.name!! // throws if null
user.name?.length // safe call, returns Int?
user.name?.length ?: 0 // elvis operator
Interop is a superpower
You don't have to choose all-or-nothing. A Kotlin class can extend a Java class, implement a Java interface, call Java methods directly, and be called from Java without any glue. That's why many teams introduce Kotlin file-by-file into an existing Java codebase, without a big-bang rewrite.
Performance
Both compile to JVM bytecode, and the JIT treats them identically. Benchmarks routinely show Kotlin within 1β3% of Java β the gap narrows further with Kotlin's inline functions and the compiler's escape-analysis improvements. On Android, Kotlin's extra runtime support adds a few hundred KB to the APK, negligible in 2026.
When Kotlin wins
- Android β Google's preferred language, with Jetpack Compose built Kotlin-first.
- Concurrency-heavy code β coroutines with structured concurrency are more expressive than raw threads; even with Java 21 virtual threads, Kotlin's
Flowand cancellation semantics are cleaner. - DSL-shaped code β build files (Gradle Kotlin DSL), HTML builders, test specifications read far better in Kotlin.
- Smaller teams that value conciseness β half the lines, half the boilerplate.
When Java is still the pragmatic choice
- Large legacy codebases with seasoned Java teams β every Kotlin line mixed in is a file that needs a Kotlin-capable reviewer.
- Environments where Oracle support contracts require "pure Java" (rare, but exists).
- When your stack is already deep in Java frameworks where annotations and reflection are tuned for Java (some older Spring integrations, some JEE containers).
- Learning β Java is a better first language; Kotlin is a better second.
Verdict
If you're starting a new JVM project in 2026 β especially an Android app β Kotlin is the modern default. If you're maintaining an existing Java codebase and the team is happy with it, there's no urgency to switch; introduce Kotlin incrementally or stick with modern Java (records, sealed classes, pattern matching, virtual threads) which has closed most of the gap.