We see teams with tens of thousands of lines of Java getting stuck on the transition to Kotlin. Google officially announced that new Jetpack APIs—Paging 3, DataStore, WorkManager with coroutines, Jetpack Compose—have a Kotlin-first surface Google's Kotlin-first initiative. Technically they can be called from Java, but with so many adapters and workarounds that productivity drops. In a typical project, 30% of code is getters, setters, and null-checks; during migration to Kotlin, that volume shrinks 2–3 times. But Android Studio's auto-converter gives only a syntactic translation, not an architectural one. We help you migrate without stopping development and without losing stability. Contact us for a preliminary work estimate.
Why You Can't Just Click "Convert Java File to Kotlin"
Android Studio can convert Java files to Kotlin automatically. The result technically compiles. But it's not Kotlin; it's a transliteration of Java into Kotlin syntax:
-
vareverywhere instead ofval— no immutability -
!!on every null reference —NullPointerExceptionis just renamed toKotlinNullPointerException - No data classes — the same POJOs with getters through
field.get() -
objectand companion objects are absent — static methods float around as extensions - No coroutines —
AsyncTaskor RxJava remain - Lambdas look like Java 8 lambdas, but without
SAM conversionfor custom interfaces
Such code gives none of Kotlin's advantages, only adds confusion. The auto-converter is a tool to start, not to finish.
What a Proper Migration Looks Like
Inventory Before You Start
The first step is a full audit of the codebase: number of classes by type (Activity, Fragment, ViewModel, Repository, Model, Util), test coverage, list of actively developed modules vs. stable ones, dependencies on Kotlin-incompatible patterns (e.g., finalize(), certain patterns with static inner classes).
Based on the audit, we build a plan: which files to convert first, which to touch last, and where parallel development on Java continues during migration.
Bottom-Up Strategy
Start with classes that have no Android dependencies: data models, utilities, constants. A Java POJO with fields, getters, and setters becomes a Kotlin data class — instant benefit: equals(), hashCode(), toString(), copy() for free.
// Before: Java POJO, 60 lines with getters/setters // After: data class UserProfile( val id: Long, val name: String, val email: String, val avatarUrl: String? = null ) Then move to the Repository layer. The key decision here is how to handle async code. If the project used RxJava, there are two paths: keep RxJava (Kotlin works great with RxJava) or migrate to coroutines + Flow. The second path is strategically better but more expensive in the moment. For actively developed repositories, we go with coroutines; for stable modules without changes, we leave RxJava until the next major refactoring.
Migrating from LiveData to StateFlow
ViewModel layer: LiveData → StateFlow + SharedFlow. This is not mandatory; LiveData works in Kotlin too, but StateFlow behaves more predictably — no magic with LifecycleOwner, no observeForever leaks, no setValue vs postValue confusion. The replacement happens in three steps: change the field type, adapt subscriptions in fragments, update tests.
Activity and Fragment are migrated last. They have the most dependencies, the most legacy code, and errors there are the most costly.
Handling Java-Kotlin Interop
Until migration is complete, Java and Kotlin classes live side by side. Kotlin calls Java without issues. Java calls Kotlin — annotations are needed:
-
@JvmStaticfor companion object methods needed from Java -
@JvmFieldfor fields without getters -
@JvmOverloadsfor functions with default parameters -
@Throws(IOException::class)if a Kotlin function throws checked exceptions
Ignoring these annotations is a common reason why auto-converted code doesn't compile from neighboring Java files.
Testing During Migration
Every converted class must pass existing tests unchanged — this guarantees the conversion didn't break logic. If tests were missing, this is the moment to write them, before conversion, while the logic is still clear from Java code. We use JUnit5 + MockK (for Kotlin classes) or Mockito (if Java test compatibility is needed).
CI must run tests on every PR. Migration without CI is chaos: you can't track which commit broke logic.
Example of a full file conversion
Suppose we have a Java class UserRepository with methods using Callback. After migration to Kotlin with coroutines, it becomes:
class UserRepository(private val api: UserApi) { suspend fun getUser(id: Long): Result<User> = runCatching { api.getUser(id) } } What Else Changes Along the Way
During migration, it makes sense to address accumulated technical debt: replace AsyncTask (deprecated since API 30) with coroutines, migrate from SharedPreferences to DataStore, update Retrofit to version with Kotlin suspend functions instead of Call<T>.
But "along the way" doesn't mean "all at once". Each such change risks regression. We compile an explicit list of "what we do within the migration"; everything else goes into the backlog for upcoming sprints.
Module Priority for Migration
| Module | Priority | Rationale |
|---|---|---|
| Models and utilities | High | No framework dependencies, safe to convert |
| Repositories | Medium | Depends on async library, requires refactoring |
| ViewModel | Medium | LiveData → StateFlow, tests need rewriting |
| Activity/Fragment | Low | Many dependencies, errors are costly |
What's Included in the Migration Work
- Codebase audit: volume assessment, test coverage, complex spots
- Staged conversion plan with priorities and timelines
- Writing tests for critical modules before conversion
- Manual architectural improvement (coroutines, data classes, null safety)
- Training the team in Kotlin patterns and new APIs
- Post-migration support: code review, interop refinement, performance optimization
Timelines
They depend on codebase size, test coverage, and whether parallel feature development is ongoing.
| Codebase | Test coverage | Estimate |
|---|---|---|
| Up to 20,000 lines of Java | Good (>60%) | 2–4 weeks |
| 20,000 – 60,000 lines | Partial | 4–8 weeks |
| 60,000+ lines | Low | 2–4 months |
The estimate is refined after the audit. Cost is calculated individually.
Migration is an investment. A team working on Kotlin with coroutines and StateFlow closes tasks faster than the same team on Java with RxJava. Not because Kotlin is magically better, but because there's less boilerplate, better analysis tools (KSP vs KAPT, Kotlin lint rules), and the library ecosystem no longer resists. Write to us for a specialist consultation and a preliminary project estimate.
We are a team with 7 years of Android development experience, having completed over 30 Java-to-Kotlin migration projects. Order a code audit and we will prepare a detailed transition plan.







