KMP 11 min read

X Rebuilt Its Android App from Scratch in Kotlin + Compose — What a Year of Clean-Slate Engineering Looks Like

On July 20, 2026, X (formerly Twitter) shipped a fully rebuilt Android app. Not a refactor, not a rewrite-by-replacement, not a new activity duct-taped into an old codebase. A clean-slate rebuild that started in August 2025 and took nearly a year to ship — and when it landed, the new app was 100% Kotlin, with Jetpack Compose handling every pixel of the UI and Kotlin Multiplatform powering the core logic behind X Chat.

I've spent the last week reading everything I could about this, cross-referencing what the team shared publicly, and thinking through it against my own experience rebuilding production apps. Five years of Android development, apps like Nodat and Samachar that went through full architecture migrations, and a healthy respect for how badly large rewrites can go — and I came away genuinely impressed by how X structured this.

This post is a case study. Not a PR piece, not speculation. Just what the rebuild was, what the engineering choices signal, and what's worth stealing if you're facing a similar decision on your own codebase.

100%
Kotlin — zero Java in the new Android codebase
KMP
X Chat: business logic shared across Android, iOS, and Web
~1 year
Engineering effort from clean slate to Play Store

Why They Rewrote Instead of Refactoring

The old X for Android had been accumulating technical debt for years — layers of Java, mixed architectures, and a codebase that reportedly made it difficult to iterate on new features without unintended regressions. This is a pattern I recognize from the Android ecosystem broadly: apps that started as Java projects, partially converted to Kotlin, with business logic scattered between fragments, activities, and whatever the "modern" approach was three major versions ago.

At some scale, the calculus flips. Patching becomes more expensive than replacing because every patch has to work around the existing constraints. X's team made that call and started from scratch rather than chasing an incremental migration path that would leave them with a permanently hybrid codebase.

That's the kind of decision that looks obvious in hindsight and terrifying in advance. Rewrites fail famously — Joel Spolsky called it the "single worst strategic mistake" a software company can make. But that wisdom applies to systems where the requirements aren't stable. X's core product loop is well-understood. The problem wasn't that they didn't know what to build; it was that the existing substrate made building it too slow. That's a different kind of rewrite, and it's one where clean-slate can genuinely win.

The Stack: Kotlin + Compose, End to End

The choice of Kotlin + Jetpack Compose as the unified stack is exactly what I'd have picked if I were starting a major Android app from scratch in 2025. Not because it's the new shiny, but because the tooling, stability, and ecosystem around this pair have finally matured to the point where you're not paying an early-adopter tax.

Jetpack Compose handles all the UI. This means declarative layouts, state-driven rendering, and — critically — Compose's pausable composition for scroll-heavy surfaces like the home timeline. If you read my post on Compose 1.11, you know pausable composition lets the runtime pause mid-composition to hit the frame deadline and resume on the next frame, which eliminates one of the more frustrating performance gaps Compose had against the View system. For a feed app where the timeline is literally the whole product, this matters more than it does in most apps.

// Compose state-driven timeline — schematic structure
@Composable
fun Timeline(
    items: LazyPagingItems<Tweet>,
    modifier: Modifier = Modifier
) {
    LazyColumn(modifier = modifier) {
        items(
            count = items.itemCount,
            key = items.itemKey { it.id }
        ) { index ->
            val tweet = items[index]
            if (tweet != null) TweetCard(tweet = tweet)
        }
    }
}

Going 100% Kotlin means no Java interop overhead, no @JvmStatic or @JvmField workarounds, no nullability annotation mismatches between Java and Kotlin types. The type system is consistent top to bottom. Coroutines are the native async primitive. You write suspend fun and structured concurrency instead of callback chains. The signal-to-noise ratio in code review improves immediately because the whole team is working in the same idiom.

X Chat and the KMP Bet

The most interesting engineering choice isn't the Android app rebuild itself — it's what X did with X Chat at the same time. The messaging feature's core was built in Kotlin Multiplatform, sharing E2E encryption logic, storage, sync mechanisms, and business rules across Android, iOS, and Web.

From JetBrains' official account, the breakdown was explicit: "X Chat: Kotlin Multiplatform across Android, iOS, and Web. E2E encryption, storage, sync, business logic: written once, shipped everywhere."

This is KMP being used exactly where it provides the strongest guarantee: correctness-critical code that must behave identically on every platform. E2E encryption is the clearest possible example. If the encryption and decryption implementations diverge even slightly between Android and iOS — because two different engineers wrote two different versions in two different languages — you get subtle interoperability bugs that are catastrophic to find in production and devastating to security.

With KMP, the encryption primitives, key derivation functions, message serialization, and storage abstraction layer are written once in Kotlin, compiled to native on Android, compiled to a native framework on iOS via Kotlin/Native, and compiled to JavaScript or WebAssembly for the web client. One codebase. One test suite. One security audit covers all platforms.

// Conceptual KMP shared module structure for X Chat's architecture
// commonMain — runs on Android, iOS, Web
expect class EncryptedStorage {
    suspend fun put(key: String, value: ByteArray)
    suspend fun get(key: String): ByteArray?
}

class MessageSync(
    private val storage: EncryptedStorage,
    private val encryptor: MessageEncryptor   // also expect/actual
) {
    suspend fun syncInbox(userId: String): List<DecryptedMessage> {
        // Pure Kotlin — compiled to every target
    }
}

The platform-specific UI still lives in each target's native layer — SwiftUI or UIKit on iOS, Jetpack Compose on Android, React or similar on Web. KMP doesn't touch the interface layer here. It handles the shared logic that has to work correctly everywhere, then hands control back to platform-native rendering for the parts where native quality matters most.

What the Rebuild Delivered

The publicly stated improvements are in the directions you'd expect from this kind of architectural refresh:

X hasn't published benchmark numbers comparing old vs. new, and I'm not going to make up figures. What I will say is that the combination of Compose's rendering model, the elimination of Java-Kotlin interop overhead, and the removal of years of architectural abstraction layers absolutely has performance headroom — whether they've fully realized it yet is a different question.

Notable gap: Spaces (X's live audio feature) was not available at launch on the rebuilt app. X acknowledged this and said it's in development. That's an important signal: in a clean-slate rebuild, some features get deferred rather than ported. If you're planning a similar rewrite, that's the honest conversation to have with stakeholders before you start.

What About Older Hardware?

X explicitly noted that performance on older Android devices is still being worked on. This is worth understanding structurally. A clean-slate Compose app tends to perform better than an equivalent View-based app on flagship hardware — better memory management, fewer unnecessary recompositions, more predictable rendering — but on older, lower-RAM devices, the Compose runtime's own overhead can matter.

This is something I've seen in my own apps. Samachar's news feed moved to a Compose-first rendering approach, and the results were excellent on Pixel 7+ class hardware but required additional tuning on devices with 2–3GB RAM. The tools are the same as always — baseline profiles, LazyColumn key stability, derivedStateOf for computed state, remember for expensive operations — but the profiling surface is different in Compose than in Views.

The fact that X is transparent about this is a good sign. It means they're still working on it rather than pretending it's solved.

What Every Android Engineer Can Take from This

Most of us aren't rebuilding apps with the staffing budget X has. But the engineering decisions here translate directly to problems I see at every scale of Android development:

On the rewrite question

The standard wisdom is "don't rewrite." That wisdom has exceptions, and one of them is when the existing architecture blocks the business. X's team didn't rewrite because they were bored with the old code. They rewrote because patches were becoming disproportionately expensive. If you can measure that — if you can show that the time cost of adding feature X to the old codebase is consistently 3× what it would take in a clean architecture — then the rewrite case gets a lot stronger. The mistake most teams make is rewriting without that measurement, chasing architectural elegance as an end in itself.

On KMP for shared logic

X's use of KMP for X Chat's business layer is a template you can apply at any scale. You don't need to be rebuilding a billion-user app to benefit from writing your encryption layer, your domain models, your sync logic, or your data transformation code once in Kotlin and sharing it across platforms. If you're shipping an Android app today and iOS is a near-term requirement, the entry cost for KMP has dropped dramatically — kotlin-multiplatform in your libs.versions.toml, a commonMain source set, and you're sharing whatever you want to share.

On Compose as the foundation

Five years into Compose, the question of whether it's ready for production is settled. The remaining question is which pieces of an existing app to migrate first. X's answer — rebuild everything at once — is not the only valid strategy. The more common approach is new screens in Compose, existing screens migrated when they're touched for other reasons. Both work. The X case shows that if you have the opportunity to start clean, Compose as a foundation is the right call.

The JetBrains Kotlin team used X's rebuild as a milestone example for the ecosystem: the new X Android app is 100% Kotlin, and X Chat's cross-platform logic demonstrates KMP at production scale. This kind of large-scale industry adoption is the clearest signal that the "is Kotlin/KMP ready" question for the app layer has been answered.

The Broader Signal

What X shipped in July 2026 is a proof point for the modern Android stack that goes beyond their own app. When a team with deep mobile engineering resources and serious performance requirements makes these bets — 100% Kotlin, Jetpack Compose end to end, KMP for cross-platform logic — and ships it to hundreds of millions of users, the technology choices get de-risked for everyone else.

I've been making the same bets in my own apps for a couple of years. Kotlin all the way, Compose where it makes sense, and KMP for anything that has to be correct on multiple platforms. The X rebuild doesn't change my calculus — it validates it at a scale I can't reach myself. And validation at that scale is worth writing about.

The app is live on the Play Store as a normal update. If you haven't checked it recently, it's worth a few minutes with a profiler running to see what a freshly written Kotlin + Compose feed app looks and feels like on your device class. That's better than any benchmark table I could publish.

Comments 0

No comments yet. Be the first to leave one!

Leave a comment