
Java to Kotlin Migration: What We Learned Rewriting 3 Enterprise Android Apps.
The good, the difficult, and the genuinely surprising parts of migrating large Java Android codebases to Kotlin - including the null-safety traps nobody warns you about.
Why we migrated and why it took longer than expected
All three projects came with the same mandate: migrate a 60,000–120,000 line Java Android codebase to Kotlin while keeping the app in production with weekly releases. The clients had been told that Kotlin's Java interoperability made this straightforward. It is - at the syntax level. At the architecture level, Java codebases carry 7–10 years of design decisions that don't map cleanly to Kotlin idioms.
The null-safety trap that will burn you
When you convert a Java class to Kotlin using Android Studio's automated conversion, every Java type becomes a platform type - neither nullable nor non-null. The compiler won't warn you. Your code compiles. And then at runtime, a NullPointerException surfaces from code that looks perfectly safe. Our rule: never trust the automated conversion output. Every platform type must be explicitly annotated, with a corresponding null-check or require() assertion. This adds 20% to migration time and prevents 90% of post-migration crashes.
What Kotlin does better immediately, with zero refactoring
Three Kotlin features that improve any migrated codebase immediately: data classes replace 50–80 line Java POJOs with 3-line definitions; extension functions let you add utility methods to Android SDK classes without inheritance chains; and when expressions replace switch statements with exhaustive pattern matching the compiler enforces. On the smallest codebase we migrated (60k lines), these three changes alone reduced line count by 22%.
The architecture changes that actually matter
The real value of Kotlin migration isn't syntax - it's the architecture unlocks. AsyncTask and Handler/Looper patterns can be replaced with Coroutines. RxJava Observable chains can be replaced with Flow. We deliver migrations in two phases: mechanical Java-to-Kotlin conversion (2–4 weeks), then architecture modernisation (4–8 weeks). Clients who skip phase two get Kotlin code that still thinks like Java.
The benchmark: before and after
Across the three migrations averaged: line count reduction of 31%, build time reduction of 18%, crash-free session rate improvement of 0.4 percentage points (from 99.1% to 99.5%) after null-safety enforcement, and new feature development velocity increase of approximately 25% as reported by in-house teams in their 90-day retrospectives.