Most PHP-to-Java migrations don't fail because the Java is bad. They fail because nobody can show the new system behaves like the old one before traffic moves.
This article compares three ways to get a PHP application onto Java 21 and Spring Boot, and asks each one the same question: what evidence do you have at cutover?
Disclosure up front: this comes from MigrateForge, which is option 3. The other two are described from their own documentation, with links, and MigrateForge's limits are included alongside its results.
Why move a PHP application to Java at all?
The triggers are rarely "Java is nicer." They are usually operational:
- Runtime end-of-life. Older PHP runtimes drop out of support, and unsupported runtimes tend to come up in security and compliance reviews.
- Hosting deprecations. Managed platforms retire old runtimes on their own schedule, not yours.
- Java standardisation. Organisations that run most services on the JVM want one hiring pool, one observability stack and one security baseline.
The systems involved are usually the ones you can't afford to break: invoicing, billing, CRM and multi-tenant SaaS cores with years of undocumented edge cases.
What are the three options?
1. Manual rewrite
A team reads the PHP, designs a Java architecture and rewrites it module by module.
- Verification: whatever the existing test suite covers, plus QA and UAT. In legacy PHP that suite is often thin.
- Main risk: regressions in behaviour nobody wrote down, like a side-effecting write in a controller, a rounding rule in an invoice total, or a webhook payload another system depends on.
2. AI coding agents and assisted transformation (AWS Kiro, AWS Transform, prompt loops)
These tools are good at generating and modernising code. They aren't one product, and the ways they verify their output differ.
AWS Kiro is an agentic engineering environment (IDE, CLI, web, mobile, and Crew). Agents plan, implement, review and verify work on your machine or in a cloud session. Its built-in quality loop is spec-driven: Feature and Bugfix Specs turn intent into requirements, design and tasks. In the IDE, Spec correctness can extract properties from EARS-formatted requirements and run property-based tests across hundreds or thousands of random cases. Kiro's stated aim is to catch bugs unit tests miss. Hooks can automatically run linters, formatters or your test suite after file changes or completed tasks, and can generate companion tests when new source files appear. Kiro supports many languages, including PHP and Java. It is a general software-engineering agent rather than a documented PHP-to-Java migration product, and replaying recorded journeys to compare database writes or outbound API calls is not documented as a built-in feature.
AWS Transform is AWS's agentic service for large-scale migration and modernisation. From the official catalog and product pages:
- PHP: an early-access
AWS/php-version-upgradethat upgrades PHP 5.x / 7.x / 8.0–8.4 applications to PHP 8.5 (or 8.3 / 8.4 if you request those branches), including Laravel and Symfony major-version bumps. It runs your existing lint, static analysis and tests before and after. This is a PHP-to-PHP runtime upgrade, not a PHP-to-Java rewrite. Upgrade targets at or below PHP 8.2 are not supported. - Java: a generally available
AWS/java-version-upgradethat takes a Java application from any source JDK to any target JDK, including Jakarta EE and Spring ecosystem updates, and validates with a build command such asmvn clean installorgradle build. - .NET: a dedicated .NET agent (and
AWS/dotnet-modernization) that ports .NET Framework to modern cross-platform .NET and upgrades later .NET versions, with local build verification and unit test execution. - Mainframe: analysis, documentation, decomposition and refactoring of IBM z/OS COBOL/PL/I and Fujitsu GS21 workloads, including automatic COBOL-to-Java refactoring. For mainframe refactor and replatform projects, AWS documents functional equivalence testing and a Compare Tool that can compare legacy and modernised results across files and databases (Db2, MySQL, Oracle, PostgreSQL and SQL Server).
- VMware and other servers: discovery, network translation into Amazon VPC, wave planning, and automated rehost to Amazon EC2, with guided testing and cutover.
- Custom transformations: organisation-specific work and language-to-language migrations, which AWS rates as very high complexity (its examples are Java→Python, JavaScript→TypeScript, C→Rust and Python→Go). You supply guidance, documentation and samples; an optional build or test command is the documented validation hook.
Transform also integrates with Kiro and other agentic IDEs through MCP, so a custom skill can be run interactively in Kiro or at portfolio scale in Transform.
AWS's own PHP-to-Java how-to is worth reading
On 29 October 2026, AWS published a technical how-to and an MIT-0 sample repository for building a repeatable PHP-to-Java migration engine with Kiro skill files (the same files can run in Transform). It auto-detects Laravel, Symfony, CodeIgniter and plain PHP, treats the original PHP application as the specification, and asks you to build a parity harness before writing the Java.
We agree with its principles: discovery before translation, build the judge before the code, fix the loop rather than the code, and keep one shared methodology that improves with every project.
It is also candid about where the sample stops. In the BookStack (Laravel) walkthrough, four of thirty-seven Eloquent models were ported as a read-only slice, and the parity harness for that slice was not built. The project's own spec calls this "the single biggest gap in the slice." The repository's Symfony example does include a parity harness, and it compares status codes, redirects and response bodies. Comparing database writes and outbound API calls is not part of that harness, and it isn't documented as a built-in capability of Kiro or AWS Transform.
That's a methodology you assemble and own, not an AWS-managed catalog transformation. It's a good one, and it lands in the same place we did: the old application is the spec, and the judge matters more than the generator.
Prompt loops in any AI coding tool are still a developer iterating with an agent until the project compiles and the visible tests pass.
- Strength: fast code generation, and well-supported same-language upgrades (Java version upgrades, PHP to PHP 8.5, .NET Framework to modern .NET). Specs, hooks, property-based tests, build/test commands and, on the mainframe path, equivalence-testing tools all raise confidence in the generated code.
- What you still need for PHP-to-Java cutover: a referee that checks the new Java system against the legacy PHP system on real behaviour, not only against a written spec or an existing test suite. Unless you build that harness yourself, replay that compares HTTP responses, database writes and outbound API calls is not documented as built-in.
- Main risk: a port that compiles, passes unit or property tests, and still differs from the legacy system in edge cases that only show up after cutover.
3. Parity-gated migration with recorded-journey replay (MigrateForge)
MigrateForge migrates PHP applications to Java 21 and Spring Boot. The output is standard Spring Boot with no proprietary runtime.
The difference is the gate. It replays recorded user journeys against both the legacy PHP and the generated Java, and compares three things:
recorded user journey | +-----------+-----------+ v v Legacy PHP app Java 21 / Spring Boot | | HTTP response <==> HTTP response Database writes <==> Database writes Outgoing API calls <==> Outgoing API calls | all match? --no--> cutover BLOCKED | yes v cutover allowed Cutover stays blocked until they match. Every wave ships an evidence report.
Scope, stated plainly:
- Available today: plain PHP and CodeIgniter 3.
- Next: Laravel, built against the first client application that needs it.
- Planned: modular output with enforced module boundaries.
- On request: service extraction, after migration, never during it.
How do the three approaches compare?
| Manual rewrite | AI agents and AWS modernisation tools (Kiro / Transform / prompt loops) | MigrateForge | |
|---|---|---|---|
| Output | Hand-written Java | AI-generated or upgraded code. Transform's managed PHP path upgrades PHP in place (to 8.5); PHP-to-Java is a skill-file methodology from an AWS how-to, not a managed catalog item | Java 21 and standard Spring Boot, no proprietary runtime |
| Cutover evidence | Existing tests + QA/UAT | Kiro: specs, hooks and (in the IDE) property-based tests against stated requirements. Transform: the build/test command you supply; .NET local build + unit tests; mainframe functional equivalence / Compare Tool. The AWS how-to describes a request/response parity harness you build yourself | Replayed recorded journeys comparing HTTP responses, database writes and outgoing API calls, plus an evidence report per wave |
| Database write verification | Manual, partial | Not documented as a built-in PHP-to-Java capability (the mainframe Compare Tool can compare databases on that path) | Separate gate: zero write-effect differences required |
| Outbound API call verification | Manual, partial | Not documented as built-in | Compared on every replayed journey |
| Cutover gate | Team judgement | Developer judgement, plus the tests, hooks or harness you configure. Transform can fail a run on a build/test command you supply | Blocked until replay matches |
| Where code runs | Your environment | Kiro: local machine or a Kiro cloud session. Transform: an AWS service; the custom CLI can operate on a local repository | Local-first: code stays on your machines; only AI requests leave |
| Lock-in | None | Generated source is yours; Kiro and Transform are AWS-hosted services | Standard Spring Boot, no proprietary runtime |
| PHP frameworks today | Any | Varies by tool and how much you build | Plain PHP and CodeIgniter 3 (Laravel next) |
What does the proof actually look like?
These are benchmarks, not client projects. Two applications, two waves each:
- WA Blast Pro: the founder's own production multi-tenant WhatsApp Business SaaS, written in plain PHP. Waves 1 and 2: 572 fixtures, all matching.
- InvoicePlane v1.7.2: a public open-source invoicing app on CodeIgniter 3.4, written by other developers. Waves 1 and 2: 286 fixtures, all matching.
Across the four waves:
- 858 fixtures at 100% parity, reported as 100/100/100: fixture, de-duplicated and route-weighted parity.
- Zero write-effect differences. That's a separate gate, not folded into the parity number.
- Zero HIGH quality findings.
- Coverage is published alongside parity, so you can see what was and wasn't exercised.
Which bugs did the verification catch before release?
Replay caught these before release:
- A delete that would also have removed a client's invoices and payments.
- A page missing its login check.
- Data visible across tenants.
- A skipped cashback payout.
It also found defects in the original PHP applications. When the old system is the spec, its own bugs surface too, and then someone has to decide whether to preserve them or fix them.
What are the honest limits?
We publish the numbers most vendors don't:
- First-pass parity, before hand fixes, on one wave: 82%. The generator is not the judge. Replay closes the gap.
- Hand fixes: 9.7 per 1,000 PHP lines. Engineers still fix things. The gate tells them where.
- Checker self-test. We plant deterministic bugs to test the checker itself. It caught 57.14% on WA Blast Pro wave 1 and 35.89% on InvoicePlane wave 1. 100% parity means every recorded fixture matched; it doesn't mean the checker catches every possible bug. That's why coverage and the self-test results sit next to the parity number.
- Framework scope. Plain PHP and CodeIgniter 3 today. Laravel is next, and will be built against the first client application that needs it.
Full evidence is at migrateforge.com/proof.
Does my source code leave my machines?
MigrateForge is local-first. Your code stays on your machines; only AI requests leave. The output is standard Spring Boot that your team owns, with no proprietary runtime.
When is a manual rewrite still the right call?
If you're deliberately redesigning the product (a new domain model, new workflows, dropped features), behavioural parity isn't your goal and a rewrite is the right tool. Parity-gated migration is for when the business needs the same behaviour on a new runtime.
How do you start without committing to a full migration?
Pricing is for India, excludes GST, and a range is not a quote. USD equivalents are on the pricing page.
- Free migration report (₹0). Size, risks, an indicative wave plan and a price range. No source code needed, and no model call.
- Assessment. ₹0.8–1.5 L under 20,000 lines; ₹1.5–3.5 L for 20,000–100,000 lines; ₹3.5–8 L above that. Credited if the migration starts within 30 days.
- Migration. ₹18,000–35,000 per 1,000 PHP lines depending on size band, paid in milestones tied to proven parity.
Who's behind it?
MigrateForge is built by Karthick Palanisamy in Coimbatore. He has 18+ years across enterprise systems: server support at Wipro, nearly six years in infrastructure (including an Exchange 2003→2007 migration across three locations), then nearly a decade as Lead Business Analyst and Scrum Master at Texcoms across ERP, CRM and digital operations. He builds full-stack systems himself in Spring Boot, Angular, Node and React.
See the replay evidence and coverage: migrateforge.com/proof
Running a plain PHP or CodeIgniter 3 application? Start with the free migration report at migrateforge.com.