Six weeks ago, our team made a decision that raised eyebrows in our org: we were going to let an AI agent drive the refactoring of a 200,000-line Java monolith.
Not assist. Not suggest. Drive.
We'd give Claude Code a CLAUDE.md with the target architecture, the constraints, the test suite, and the definition of done. We'd run it in autonomous mode overnight. Engineers would review in the morning.
I'm writing this because the results were not what any of us predicted — not the optimists, not the skeptics. Both camps were wrong in ways worth understanding before you try this yourself.
This is a real account of work at a client engagement (details anonymized). The codebase is a 12-year-old Spring Boot monolith — financial services, ~200K lines of Java, 68% test coverage. The team is 8 engineers. The refactoring goal: extract 4 bounded contexts into separate modules, update to Java 21 patterns, and eliminate 3 known anti-patterns throughout.
Why We Tried This
Let me tell you what convinced a risk-averse financial services engineering team to run an AI agent against production source code.
The realistic alternative was worse. Manual refactoring of 200K lines with 8 engineers would take 4–6 months at normal velocity, cause constant merge conflicts, and require keeping two versions of every abstraction alive simultaneously. We'd done this before. It's miserable.
The test coverage was real. 68% isn't great, but the critical paths — payment processing, account management, audit logging — were at 94%. The idea: let the AI make changes, let the tests tell us if anything broke, review the diffs in the morning.
We'd already seen what it could do. Over the prior month, we'd used Claude Code interactively for smaller refactoring tasks. Extracting a service class here, converting a DTO to a record there. It was faster than we expected and the mistakes were easy to catch.
The question was whether it would scale. The honest answer: partially.
The Setup
We spent a week before touching any code configuring the environment. This turned out to be the highest-ROI week of the project.
CLAUDE.md
Our CLAUDE.md was 400 lines — deliberately detailed. It included:
## Target Architecture
The monolith is being extracted into 4 modules:
- `core-banking`: Account, Balance, Transaction entities + repositories
- `payment-processing`: PaymentRequest, PaymentGateway, RetryPolicy
- `audit-trail`: AuditEvent, AuditRepository, AuditQueryService
- `customer-portal`: CustomerProfile, Preferences, NotificationSettings
Each module must:
- Have no compile-time dependencies on other modules (only via interfaces)
- Own its own database schema (prefix: `banking_`, `payment_`, `audit_`, `portal_`)
- Expose a public API via Spring ApplicationEvents or explicit service interfaces — never direct bean injection across module boundaries
## What Is a Correct Change
A change is correct if:
1. All existing tests pass (`./mvnw test` exits 0)
2. The moved class is not referenced from outside its target module
3. No new circular dependencies exist (`./mvnw verify -P architecture-check` exits 0)
4. The CHANGELOG.md entry describes what moved and why
## What Is NOT Correct (Do Not Do These)
- Adding @SuppressWarnings to make compilation pass
- Deleting tests to make the test suite pass
- Creating adapter classes that duplicate logic "temporarily"
- Importing from `com.company.monolith.*` in any new module (this is the boundary you are enforcing)
The "what NOT to do" section was the most important part. Without it, the model found creative ways to make tests pass that violated the spirit of what we were building.
Verification Script
Every autonomous run ended with a verification script that Claude Code was instructed to run before committing:
#!/bin/bash
set -e
echo "=== Running test suite ==="
./mvnw test -q
echo "=== Checking architecture boundaries ==="
./mvnw verify -P architecture-check -q
echo "=== Checking for forbidden imports ==="
grep -r "com.company.monolith" src/main/java/com/company/modules/ && echo "FORBIDDEN IMPORTS FOUND" && exit 1
echo "=== All checks passed ==="
If any check failed, Claude Code was instructed to rollback (via git checkout .) and document why it couldn't complete the task. This was critical — it prevented silent failures from accumulating.
What It Got Right
Let me be specific. These were the genuine wins.
Mechanical Transformations at Scale
Moving a class from one package to another while updating all references — Claude Code does this better than any engineer. Not because it's smart, but because it's patient and exhaustive.
We had 143 classes to relocate across the 4 modules. Manually, this is a 3-day job that produces 300+ merge conflicts. Claude Code did 127 of them correctly in the first overnight run. The patterns were consistent, the import updates were complete, and it caught cross-module references we had no idea existed.
That's 89% success on the most mechanical part of the work. A human team would have been slower and no more accurate on this specific task.
Java 21 Modernization
Within the context of each moved class, Claude Code applied modern Java idioms correctly and consistently: