# I Migrated a 350k-Line Java/JSP Application to TypeScript in Five Days

> Source: <https://gokulakrishna.co/2026/07/31/migrated-350000-line-java-jsp-application-typescript-five-days/>
> Published: 2026-07-31 06:30:58+00:00

I recently migrated a 350,000-line Java/JSP application to TypeScript using AI agents.

Our original estimate for a conventional migration was three engineers and close to a year. Instead, one engineer orchestrating pools of up to 20 sub-agents at each stage got the application running in a development environment in five days.

The headline sounds like a story about faster code generation. It is not.

**The decisive factor was verification.** Once the agents had a reliable way to compare the old and new systems, parallelism became useful rather than chaotic.

### 350,000

lines of Java/JSP

### 5 days

to a running development environment

### 20

sub-agents in each stage

## Why parallel agents were only half the solution

Jarred Sumner’s account of the Bun rewrite gave me useful ideas for running agents in parallel. Bun had an important advantage: a language-independent TypeScript test suite. The implementation could change while the expected behaviour remained measurable.

Our application had no equivalent safety net.

Only about half of the existing tests worked. Many had weak or missing assertions, and others depended directly on infrastructure. A large agent pool could generate code quickly, but without an independent oracle it could also produce 20 plausible versions of the wrong system.

We needed a way to answer a simple question at every stage: *does the new application behave like the one already running?*

## The migration sequence

- Database
- APIs
- UI
- Business logic
- Improvements

That order was deliberate. Each stage created a more stable foundation for the next, and it limited the number of variables that could change at once.

### 1. Reconstructing the database contract

The legacy application did not have a reliable database schema we could treat as authoritative.

We reconstructed the application-facing Oracle schema from the SQL queries embedded throughout the codebase. The agents identified tables, columns, joins, constraints and data assumptions from the queries, then we validated the reconstructed contract against the running system.

This was more than a database exercise. The schema defined the vocabulary shared by every API, page and business rule that followed. Starting anywhere else would have forced the agents to build on guesses.

### 2. Rebuilding the APIs

Once the data contract was stable, agents could migrate API components in parallel. Each work item was narrow: rebuild a component, exercise it against both systems, compare the result and resolve the discrepancy.

This gave the UI migration a dependable backend target instead of asking agents to infer behaviour simultaneously at every layer.

### 3. Migrating the UI

The UI required more than checking HTTP status codes. We needed to know whether the same data appeared, whether the same elements were present and whether the same interactions produced the same outcomes.

This is where the parity harness became the centre of the migration.

## Building a parity harness where tests were missing

The parity harness ran the legacy and TypeScript applications side by side and compared them at three levels:

- API responses and resulting database state
- Page elements and screenshots
- User interactions in both applications

The harness did not need to understand the intended design of the whole system. It needed to expose observable differences.

That distinction mattered. It turned the running legacy application into an executable specification, including behaviours that were not documented and edge cases that the surviving tests did not cover.

## Parity before correctness

During the migration, we treated existing bugs as expected behaviour.

That may sound counterintuitive, but fixing bugs while changing languages, frameworks and architecture would have made failures ambiguous. When a result differed, we would not know whether we had introduced a regression or intentionally changed behaviour.

First reproduce the system. Then improve it.

We recorded known defects and fixed them only after achieving parity. This separated migration risk from product change and made discrepancies much easier to diagnose.

## How the agent pools worked

At each migration stage, a pool of up to 20 sub-agents handled bounded pieces of work. Their jobs generally fell into three categories:

- Rebuild a component in TypeScript.
- Compare the old and new implementations through the parity harness.
- Investigate and fix the differences.

The primary engineering task shifted from writing every line to designing work packets, maintaining dependency order, interpreting failures and improving the verification loop.

Parallelism helped because the work was decomposable. The harness helped because every agent could check its own result against the same external standard.

Without that feedback loop, adding more agents would have increased review load. With it, discrepancies became actionable inputs for the next iteration.

## The cost

The migration consumed approximately **100 million output tokens**.

At Fable 5 output-token pricing, that was roughly **US$5,000**, excluding input tokens and other usage costs.

That number needs context. It is not the total cost of productionizing a modernized core system, and getting the application running in development is not the same as completing security review, performance testing, deployment and operational handover.

But it changes the economics of the first major migration milestone. A project estimated at three engineers for close to a year reached a working development environment with one engineer in five days.

## What made the five-day migration possible

The result did not come from asking a model to “rewrite the application in TypeScript.”

It came from combining five practices:

- Establish an observable source of truth before scaling code generation.
- Migrate in dependency order so every stage has a stable foundation.
- Give agents bounded work with clear inputs and outputs.
- Preserve legacy behaviour until parity removes ambiguity.
- Make comparison and repair part of every agent’s loop.

The parity harness was the force multiplier. It reduced the amount of trust required, made parallel work measurable and let agents detect many of their own mistakes before human review.

**AI agents supplied the throughput. Verification converted that throughput into progress.**

## What this means for legacy modernization

The biggest shift may not be that AI writes code faster. It is that migration work can be reorganized around machine-executable feedback.

Systems once considered too large, too poorly documented or too risky to touch may become practical candidates when their observable behaviour can be captured and compared incrementally.

That does not remove the need for engineering judgment. It makes judgment more leveraged. The engineer still decides the sequence, defines the boundaries, builds the oracle and determines when parity is good enough to move forward.

As migration time and cost continue to fall, the question for many organizations may change from “Can we afford to modernize?” to “What would we need to verify it safely?”

**Would you consider modernizing your core systems?**

[Get in touch](https://gokulakrishna.co/contact/) if you are exploring an AI-assisted migration or need help designing a verification strategy.
