The starting point
The app behind these notes is a HIPAA-compliant community and collaboration platform, live on Android for over a decade and used by more than 1.3 million users and organizations. The codebase had grown the way most long-lived codebases do: roughly 391,000 lines of Java and XML views in a single module, with Activities, MVP, and RxJava layered through it, one era at a time.
None of that made it a bad app. It made it a slow one to change. Incremental builds took 88 seconds, every feature touched code it didn't own, and each new hire inherited all of it at once. A rewrite was the obvious suggestion and the wrong one: the product had a roadmap, paying organizations, and no appetite for a feature freeze.
The approach: strangler fig, screen by screen
We ran the migration as a strangler fig. The modular architecture grew around the monolith and took it over one screen at a time, while the app on the store kept releasing on its normal cadence.
The order of operations mattered more than any individual technique:
- The module graph came first. Before any UI moved, we designed the target graph: feature modules behind clear API surfaces, shared core modules for design, data, and networking, and dependency injection rules that made illegal imports impossible rather than discouraged.
- Each screen moved whole. A screen left the monolith as a unit: Java to Kotlin, XML views to Jetpack Compose, MVP and RxJava to MVVM with ViewModel, Coroutines, and Flow. No screen lived half-migrated, which is where strangler figs usually rot.
- Features kept shipping. New work landed in new modules from day one. The monolith only ever shrank.
- Release discipline was the safety net. Migrated screens shipped behind backend-driven feature flags with fast rollback that needed no app release. Test coverage grew four-fold during the migration, with JUnit, Mockito, Espresso, and Appium suites wired into CI so every change was exercised before it reached users.
The result of that ordering: 100% uptime across the whole migration, and a build that got faster every month the work went on, ending 93% quicker for the entire team.
Where the agentic workflows fit
This migration is also where our AI-native operating model was forged. The rule we settled on: agents do the volume, humans own the structure. Nothing architectural shipped on autopilot.
- Agentic refactors with review gates. AI coding agents carried the mechanical weight of conversion work, inside guardrails: every structural decision designed and reviewed by a senior engineer, every change through the same CI as human-written code.
- AI on release health. Scheduled agent pipelines run daily health checks against crash reporting, triage new crashes, and produce candidate fixes. That system cut crash-affected users by 80% within three months and holds the app at 99.7% crash-free.
- AI in the whole lifecycle. Code review, crash triage, visual QA against emulators, and release automation all run agent-assisted, with human sign-off where it counts.
What we'd tell you before you start
- Draw the module graph before you touch a screen. Boundaries are the migration; Compose is just the payoff.
- Migrate state management with the screen, not before it. Half-moved architecture costs more than un-moved architecture.
- Treat build time as a first-class metric. It is the compounding return that keeps the team invested for the months the migration takes.
- Put the rollback path in place before the first migrated screen ships, not after the first incident.
- Give AI the volume and keep the steering. Agentic tools made this migration fast; review gates made it safe.