Legacy applications have a way of quietly draining engineering capacity through the maintenance they demand. Aging tech stacks and convoluted architectures lead to brittle systems that create immense overhead for teams. The natural instinct of engineers is to rewrite everything in a robust, modern and maintainable way. But there are real impediments that stand in the way:
Prioritization. The business case is hard to make; the service technically works and there is no new feature attached to the effort.
Scope. These rewrites are large enough that they go beyond the time allotted for technical debt.
Skillset. For instance, if an application has a significant UI layer, a backend-heavy team may not have the expertise to confidently take on the challenge.
Our team had two problematic applications coming in last year. One was a decade-old CRUD application sitting on top of a fractured data layer. The other was a public-facing web service written in a stack nobody on the team could maintain anymore. Over the past year, we used AI-assisted development to overcome said impediments and rewrite both. In this post we want to share what worked, what didn’t, and what we learned about the role of engineering judgment when AI is in the loop.
Service 1: The Decade-Old CRUD App
The Problem
This application was an internal data management tool built on Ruby on Rails, backed by a single relational database. Over the years, domain boundaries shifted across teams, and what was once one database became three: the original (still the source of truth) plus two additional databases owned by other domains. Data flowed from the original to the others through sync pipelines that the team had to build and maintain.
Keeping multiple systems consistent is one of the harder problems in software engineering, and ours was no exception. We estimated roughly 30% of engineering time on this service went toward sync-related issues: debugging inconsistencies, building detection tooling, firefighting when things drifted. The fractured data layer further complicated new feature work. Almost every change had to be implemented against both the original database and the target databases, or the team had to devise workarounds to avoid double implementation. What started as a data migration problem became a tax on every new feature.
Our goal was to replace the monolithic application with a frontend-only service that talks directly to Domain A and Domain B through their APIs. This would eliminate the original database entirely, and with it, the maintenance nightmare that occupied so much of our time.
The Rewrite
In late 2024, AI coding tools had matured to a point that justified a proper spike to understand the feasibility of a rewrite. Our team is primarily backend engineers with little frontend experience, so early experiments naturally focused on UI scaffolding: feeding screenshots of existing pages and asking the model to replicate them in a modern framework. The results were promising enough that by mid-2025, the team committed to a full rebuild.
We set a few constraints up front:
Strict visual parity. No UX redesign. The goal was a drop-in replacement, not a product overhaul.
Approved stack only. We chose a modern React framework that was already sanctioned within the organization.
Stick to one model. When multiple engineers use different models, the output style and patterns drift. We settled on Claude Sonnet 4.6 specifically because it was precise about scope and reliable at making targeted edits.
AI as a pair programmer, not a generator. Early experiments taught us that vague prompts produced messy output. The workflow that stuck was treating the model like a junior pair: give it step-by-step instructions, review its output like a pull request, and course-correct frequently.
The rewrite followed a page-by-page strategy:
- Screenshot an existing page
- Prompt the model to replicate the layout and behavior in the new stack
- Review the generated code, refactor shared components, and wire up real data sources
- Manually verify parity against the original

Where AI helped most was in the repetitive middle of the work: generating boilerplate, cross-component refactoring, and explaining unfamiliar patterns in the legacy codebase. Where we deliberately kept control was in the parts that required system-level understanding: resolving data dependencies between the old databases, determining which downstream services to integrate with, and smoke-testing against real production scenarios. Those decisions stayed entirely with the engineers.
The Result
The core rebuild took about three months with one to two engineers, followed by another three months of intensive testing and bug fixes. All of this happened without pulling anyone off the existing roadmap. For context, the original application had taken a dedicated team of six engineers and many years to build. A rewrite is inherently easier than the original build because you already know what the system needs to do, so the comparison is not apples to apples. But even so, the reduction in time and effort was striking.
Service 2: The Orphaned Web Service
The Problem
Our second target was a public-facing email preference service: a small full-stack application with a frontend layer. Functionally, it was simple. A user clicks a link in an email, a form renders, they submit their preference, and an event gets published downstream. It was the kind of service you write once and it just works, no new features, no product development, just maintenance.
The trouble was the tech stack. The application was built in Elixir and Erlang with a Node.js frontend layer, and every component had fallen far behind:
- The team had lost Elixir expertise over the years
- Error messages were ambiguous, crashes were inexplicable, and reproduction was nearly impossible
- 80 Dependabot alerts had piled up, many requiring dependency upgrades with breaking changes that nobody had the context to safely apply
Our team is a Java shop. We follow consistent conventions, share build tooling, and get dependency updates as part of regular maintenance cycles. Rewriting this service in Java meant it would stop being a special case and start being just another service in the fleet.
The Rewrite
We started by asking the coding assistant to analyze the existing codebase and assess feasibility. The analysis gave us confidence: the application was small (under 2,000 lines of code across a few dozen modules), stateless (no database, no persistent state), and had a focused domain with direct equivalents in our Java ecosystem.
Next, we pointed the assistant at one of our well-structured Java services and asked it to extract the conventions, project layout, build configuration, and patterns we wanted to replicate. This gave the rewrite a concrete target to match, not just “rewrite in Java” but “rewrite in Java the way our team writes Java.”
From there, we worked through a phased build plan:
Phase 1: Project skeleton, cryptographic utilities, and event publishing. At the end of this phase the application could start, decrypt incoming parameters, and publish events downstream.
Phase 2: Request filters, controllers, and business logic. All endpoints responding correctly (without templates wired up yet).
Phase 3: Template migration and static assets. Port all frontend templates to the Java templating engine, copy stylesheets and scripts unchanged. UI renders and looks identical.
Phase 4: Internationalization. Convert all language files to the Java resource bundle format and wire up locale switching.
Phase 5: Test suite. Port the existing tests to JUnit, verify coverage.
Phase 6: Containerization, Kubernetes manifests, and CI/CD pipelines.
The Result
Start to finish, the rewrite took one week! Going live was painless thanks to a Kubernetes ingress cutover strategy suggested by the coding assistant: deploy the new service alongside the old one with an additional hostname for testing, validate, then remove the old service’s ingress so the existing hostname automatically routes to the new application. No broken URLs, no client-side changes, no maintenance window.
What We Learned
These are two very different services (one large and UI-heavy, one small and stateless) but the takeaway is the same.
AI did not remove engineering judgment from the process. The constraints were still ours to set. We decided what to rewrite, chose the target architecture, defined the migration strategy, and ran the testing. Code review happened exactly the same way it would for any other change. The model never made a single architectural decision on our behalf.
What changed was the economics. Work that previously required a dedicated team and open-ended timelines now fit inside the capacity we already had. A decade-old application that seemed permanently untouchable got rebuilt by one to two engineers alongside their normal workload. A service that nobody could safely maintain got rewritten in a week.
If your team has legacy services sitting in the “too expensive to fix” pile, the cost of a rewrite may be worth re-evaluating. Not because AI will do it for you, but because it compresses the mechanical parts of the work enough that the effort fits into time and headcount you already have. The hard parts (understanding the system, setting constraints, validating correctness) are still yours. That is exactly where they should be.
