On this page
- The short answer
- How Android got here
- Side-by-side: the differences you'll feel every day
- Kotlin vs Java comparison table
- What about performance?
- Mixing Kotlin and Java in one project
- Which should you choose? Three real scenarios
- Migrating from Java to Kotlin safely
- Common mistakes when switching to Kotlin
- Frequently asked questions
- Conclusion
"Kotlin or Java?" is still one of the first questions new Android developers ask, and teams with older apps ask a version of it too: is migrating worth the effort? The honest answer has changed over the years. In 2026 it depends less on the languages and more on where the Android ecosystem is heading.
This comparison is for beginners choosing a first language, Java developers moving to Android, and teams maintaining Java codebases. You'll see the practical differences in side-by-side code, where Java still matters, what interop looks like in a mixed codebase, and a recommendation for each situation.
The short answer
Learn Kotlin first for Android. Google announced a Kotlin-first approach to Android in 2019. New Jetpack APIs, Jetpack Compose, the official samples and most documentation assume Kotlin. Java remains fully supported for running and maintaining apps, and it is still worth being able to read, because many production codebases and libraries contain it.
That recommendation rests on concrete technical reasons, not fashion. The rest of this article explains them.
How Android got here
Android apps were originally written in Java, using a subset of the Java standard library and Android's own runtime (Dalvik, later ART). Kotlin, created by JetBrains, compiles to the same JVM bytecode, which Android's build tools then convert to DEX bytecode. That shared target is why Kotlin and Java can live in the same project, call each other, and ship in the same APK.
- 2017: Google announced official Kotlin support for Android.
- 2019: Google declared Android development "Kotlin-first".
- Since then: Jetpack Compose (Kotlin-only), coroutine-based APIs across Jetpack, and Kotlin-specific extensions (KTX libraries) have become the default way to build Android apps.
Side-by-side: the differences you'll feel every day
1. Null safety
NullPointerException has historically been one of the most common causes of Android crashes. Kotlin builds nullability into the type system. A String can never be null, and a String? must be handled before use. The compiler enforces this.
// Nothing stops user or getAddress() from being null at runtime.
String city = user.getAddress().getCity(); // possible NullPointerException
// Defensive version
String city = "Unknown";
if (user != null && user.getAddress() != null && user.getAddress().getCity() != null) {
city = user.getAddress().getCity();
}// The type tells you what can be null; the compiler forces you to handle it.
val city: String = user?.address?.city ?: "Unknown"Kotlin can't prevent every NPE. Values coming from Java code without nullability annotations are "platform types" whose nullability the compiler doesn't know. But in a Kotlin-heavy codebase, null-related crashes become much rarer and are easier to trace.
2. Data models
// Records remove boilerplate. Android's toolchain supports the record syntax,
// but check your AGP / minSdk combination; many Java codebases still use classic POJOs.
public record Article(long id, String title, String author) {}data class Article(
val id: Long,
val title: String,
val author: String? = null, // default values and nullability in one place
)
val updated = article.copy(title = "New title") // immutable update, very common for UI stateModern Java has narrowed this gap with records. Kotlin's data classes still add copy(), default arguments and named parameters. These matter a lot for immutable UI state in ViewModels.
3. Asynchronous code
This is the biggest practical difference on Android. Kotlin coroutines let you write sequential-looking asynchronous code with built-in cancellation tied to lifecycles such as viewModelScope. Java has no language-level equivalent on Android, so Java code typically uses callbacks, executors, ListenableFuture or RxJava.
repository.loadUser(userId, new Callback<User>() {
@Override public void onSuccess(User user) {
repository.loadOrders(user.getId(), new Callback<List<Order>>() {
@Override public void onSuccess(List<Order> orders) {
mainHandler.post(() -> render(user, orders));
}
@Override public void onError(Throwable t) { mainHandler.post(() -> showError(t)); }
});
}
@Override public void onError(Throwable t) { mainHandler.post(() -> showError(t)); }
});
// Cancelling both requests when the screen closes needs extra bookkeeping.viewModelScope.launch {
try {
val user = repository.loadUser(userId) // suspend function, main thread not blocked
val orders = repository.loadOrders(user.id)
_uiState.value = UiState.Success(user, orders)
} catch (e: IOException) {
_uiState.value = UiState.Error(e.message)
}
}
// When the ViewModel is cleared, viewModelScope cancels this work automatically.4. UI toolkit
Jetpack Compose is written for Kotlin. Composable functions rely on the Kotlin compiler plugin and on Kotlin language features such as trailing lambdas. You can't write Compose UI in Java. If you choose Java, you are choosing XML Views for your UI, which is still supported but is no longer where Google invests most of its new UI work.
5. Expressiveness
Extension functions, when expressions, sealed types, scope functions (let, apply), and collection operators make typical Android code shorter. Shorter isn't automatically better, but less boilerplate leaves less room for bugs to hide.
// Add behaviour to a type you don't own, without inheritance or a Utils class.
fun Context.showToast(message: String) =
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
// Exhaustive handling of UI state: the compiler errors if a case is missing.
fun label(state: UploadState): String = when (state) {
UploadState.Idle -> "Choose a file"
is UploadState.InProgress -> "Uploading ${state.percent}%"
is UploadState.Failed -> "Failed: ${state.reason}"
UploadState.Done -> "Uploaded"
}Kotlin vs Java comparison table
| Aspect | Kotlin | Java |
|---|---|---|
| Google's stance | Preferred ("Kotlin-first") | Supported for apps and libraries |
| Null safety | In the type system, checked at compile time | Annotations and discipline; NPEs at runtime |
| Async | Coroutines + Flow, lifecycle-aware scopes | Callbacks, executors, futures, RxJava |
| Modern UI | Jetpack Compose | XML Views only |
| Boilerplate | Low | Higher, improving with newer Java features |
| Runtime performance | Comparable; same DEX bytecode target | Comparable |
| Build speed | Kotlin compilation has historically been slower; the K2 compiler improved it | Generally fast |
| Learning curve | More concepts (coroutines, scope functions, DSLs) | Simpler syntax, more verbose |
| Multiplatform | Kotlin Multiplatform shares code with iOS, desktop, web | Not applicable |
| Existing code | Most new Android code | Large amount of legacy app and library code |
What about performance?
Because both languages compile to the same bytecode, runtime performance differences in real apps are usually negligible compared with architecture choices, I/O, rendering and memory use. A few things to know:
- Some Kotlin features generate extra bytecode, such as default arguments, lambdas captured in non-inline functions, and companion objects. R8 optimises much of this in release builds.
inlinefunctions remove lambda allocation overhead for higher-order functions such asmapandfilter.- The Kotlin standard library adds some size, which R8 shrinking reduces.
If an app is slow, the language is very rarely the reason. Profile before blaming Kotlin or Java. Our Android performance guide shows where the real bottlenecks usually are.
Mixing Kotlin and Java in one project
Interoperability is one of Kotlin's strongest design goals. In a mixed project you can call Java from Kotlin with no ceremony, and call Kotlin from Java with a few annotations that make the Java side feel natural:
object PriceFormatter {
// @JvmStatic: Java can call PriceFormatter.format(...) instead of PriceFormatter.INSTANCE.format(...)
// @JvmOverloads: generates Java overloads for the default parameter.
@JvmStatic
@JvmOverloads
fun format(amount: Double, currencySymbol: String = "โน"): String =
"$currencySymbol${"%.2f".format(amount)}"
}String total = PriceFormatter.format(1499.0); // uses the default symbol
String usd = PriceFormatter.format(19.99, "$");When Kotlin calls a Java method that lacks @Nullable or @NonNull annotations, the return type is a platform type (shown as String! in the IDE). Kotlin lets you treat it as non-null, so if Java returns null you get an NPE at that point. Annotate your Java APIs, or treat their results as nullable in Kotlin.
Which should you choose? Three real scenarios
Beginner starting Android
- Problem
- Limited time; needs skills that match current jobs and docs.
- Choice
- Kotlin + Jetpack Compose.
- Why
- Matches the official learning material and modern job descriptions.
- Trade-off
- You'll need to learn to read Java later for older code. That's much easier than the reverse.
Experienced Java backend developer
- Problem
- Strong Java skills, new to Android.
- Choice
- Kotlin, using your Java knowledge as a bridge.
- Why
- The JVM concepts transfer directly; Kotlin syntax takes days to weeks, not months.
- Trade-off
- Coroutines need a genuine mental-model shift from threads and futures.
Team with a large Java app
- Problem
- Stable Java code; new features to build.
- Choice
- Write new code in Kotlin; migrate old files only when you touch them.
- Why
- Interop makes gradual adoption low-risk, with no "big rewrite".
- Trade-off
- Two languages in one codebase for a while; agree on conventions and nullability annotations.
Migrating from Java to Kotlin safely
- Add Kotlin to the build by applying the Kotlin Android Gradle plugin. Recent Android Studio templates include it by default.
- Start with new code and tests. Write new features and unit tests in Kotlin. Tests are low-risk and let the team learn.
- Annotate Java nullability first (
@Nullable/@NonNull) on classes you'll call from Kotlin. - Convert files you're already changing with Android Studio's Convert Java File to Kotlin File, then review the result by hand. The converter produces working but often non-idiomatic code, with things like overuse of
!!andlateinit. - Keep commits small: one conversion per commit, behaviour unchanged, tests passing. Then make idiomatic improvements in a follow-up commit.
Common mistakes when switching to Kotlin
- Writing "Java in Kotlin syntax": mutable
vars everywhere, getters and setters, utility classes instead of extension functions. - Sprinkling
!!to silence the compiler. Each one is a potential crash; model nullability properly instead. - Overusing scope functions: nested
let/apply/alsochains hurt readability. A plainifis often clearer. - Using
GlobalScopefor coroutines. It isn't tied to any lifecycle, so the work can leak. UseviewModelScopeorlifecycleScope.
Frequently asked questions
Is Java dead for Android?
No. Java is supported, the Android framework itself contains a great deal of Java, and many apps and libraries are written in it. But new Android APIs and tooling are designed Kotlin-first, so Java is no longer the best choice for new app code.
Do I need to learn Java before Kotlin?
No. Kotlin is a complete language on its own. Being able to read Java helps when you work with older code or Java-only libraries, and Kotlin developers usually pick that up naturally.
Is Kotlin slower than Java on Android?
Not in any way that matters for typical apps. Both compile to DEX bytecode, and R8 optimises release builds. Architecture, I/O and rendering have far more impact on performance than the language.
Can I use Jetpack Compose with Java?
No. Compose relies on the Kotlin compiler plugin and Kotlin language features. A Java Activity can host Compose content written in Kotlin, but the composables themselves must be Kotlin.
Will knowing Kotlin help outside Android?
Yes. Kotlin is used for backend development (for example with Spring or Ktor) and for sharing code across platforms with Kotlin Multiplatform.
Conclusion
For Android in 2026, Kotlin is the default and Java is the legacy you should be able to read. Kotlin gives you compile-time null safety, coroutines for asynchronous work, and access to Jetpack Compose, and it interoperates with Java so smoothly that no existing project needs a risky rewrite.
Next step: if you're a beginner, follow Stage 1 of the Android roadmap. If you already know Kotlin basics, go straight to Kotlin Coroutines and Flow Explained, which covers the feature that most changes how you write Android code.
Official references
๐ฑ Apps built by Yunok Tech
Real Android apps on Google Play, free to download.
Walzi4K wallpapers for lock and home screenGet it โธ
NotiVaultNotification history: recover dismissed notificationsGet it โธ
Ready to build real Android applications?
Kotlin fluency makes everything else in Android easier, from coroutines and Flow to Compose and clean architecture. The fastest way to build that fluency is to write real Kotlin every day and get feedback on it.
Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.
Explore the Android course and take your next step as an Android developer.