Legacy Java Modernization & End-of-Life Migration

ModernLift · ·10 min read

Oracle ended free public updates for JDK 8 in January 2019 and Premier Support in March 2022, and Java EE has been frozen since Oracle handed it to the Eclipse Foundation as Jakarta EE — meaning the old javax.* APIs no longer evolve. Modernizing legacy Java means moving the application onto a current, supported JDK and framework, and off aging app servers, ideally slice by slice with each slice proven to behave identically before cutover.

A legacy Java application is rarely just “old code.” It’s a decade of business logic written against Java EE APIs that no longer move, running on a JDK that stopped getting free patches years ago, hosted on an application server whose licensing and weight are a tax nobody questions anymore. “Just upgrade Java” understates the job — because the version of the language, the framework generation, and the server are usually entangled, and pulling one forward drags the others with it.

How much runway legacy Java has left

The Java platform retires on a public schedule, and the versions still common in enterprise production sit at very different points on it:

ComponentStatusWhat it means
Oracle JDK 8Public updates ended Jan 2019; Premier Support ended Mar 2022Frozen; security fixes require a paid subscription (Extended Support to Dec 2030)
Java EE (javax.*)Transferred to Eclipse Foundation 2017; no longer evolvesA dead namespace — superseded by Jakarta EE
Jakarta EE 9+ (jakarta.*)Current; actively maintainedThe supported forward path for enterprise Java
Modern LTS JDK (17, 21)SupportedCurrent, patched, and where new features land

Dates are from Oracle’s Java SE Support Roadmap and the Eclipse Foundation’s Jakarta EE history. The line that matters is the same one the SQL Server world knows: a runtime the vendor has stopped freely patching is not “old but fine” — it’s a platform where the next vulnerability has no fix unless you are paying for one, and where every year on it makes the eventual move harder.

What end of life actually means for a Java estate

The risk of staying on legacy Java is not a single dramatic outage; it’s a slow tightening on three fronts.

  • The security floor erodes. An unsupported or unsubscribed JDK, an old Java EE stack, and aging dependencies mean known CVEs accumulate with no clean patch path. In a regulated environment — PCI DSS, SOC 2, HIPAA — an unpatchable Java runtime in scope is an audit finding waiting to be written.
  • The framework dead-ends. The javax.to jakarta. break means modern libraries, security patches, and tooling increasingly target the new namespace. Staying on Java EE quietly cuts the application off from the ecosystem it depends on.
  • The team thins out. Engineers want to work on a current stack. A team maintaining a frozen Java EE app on an old JDK gets harder to staff and slower to change, and the institutional knowledge of what the code actually does walks out the door over time.

The migration options

There is no single right move; there’s a right move for your application, and it turns on how deeply the code is bound to the old framework and server.

  • Upgrade the runtime in place — move to a current LTS JDK (17 or 21) while keeping the existing architecture. The lowest-risk option when the code is reasonably portable and the framework already supports the new JDK. It clears the support and security risk; it does not, on its own, address why the system is slow to change.
  • Migrate the framework to Jakarta EE — cross the javax.to jakarta. boundary and onto a maintained framework generation. More involved, because the namespace change touches imports, configuration, and dependencies across the codebase, but it reconnects the application to the living ecosystem.
  • Re-platform off the heavyweight app server — move from WebLogic or WebSphere onto a lighter, current runtime. The most transformative path, and the one that sheds the licensing and operational weight of the legacy server, but it has to bring the deployment model, the integration surface, and the configuration with it.

The decision rarely turns on the JDK alone — it turns on the framework and server coupling: how much of the application assumes Java EE APIs, container-managed services, and server-specific behavior. That coupling is what makes legacy Java modernization a project rather than a recompile.

What actually breaks moving off Java 8

“Just recompile on a newer JDK” underestimates the work, because the JDK changed in ways that touch real applications between 8 and the current LTS releases. The common surprises, all of them public and well-documented:

  • Java EE modules were removed from the JDK in Java 11. JEP 320 deleted the bundled java.xml.bind (JAXB), java.xml.ws (JAX-WS), CORBA, and JTA modules that a lot of Java 8 code assumed were just there. On Java 11 and up they have to be added back as explicit third-party dependencies, and code that relied on them failing silently until runtime is a classic upgrade trap.
  • JDK internals are now strongly encapsulated. From Java 16 (JEP 396) and Java 17 (JEP 403), reflective access into sun.misc.* and other internal packages is blocked by default instead of merely warned about. Older libraries that reached into JDK internals stop working until they are upgraded to versions that do not.
  • Pieces of the platform were deleted outright. The Nashorn JavaScript engine was removed in Java 15, the applet API is gone, and the SecurityManager was deprecated for removal in Java 17. Any application leaning on these needs a real replacement, not a flag.
  • The javax.* to jakarta.* rename is pervasive. Crossing from Java EE onto Jakarta EE 9 or later is not a version bump. Every import, a lot of configuration, and the dependency coordinates of the libraries themselves change from javax to jakarta. It touches the whole codebase, and third-party libraries must be on versions that already made the move. This is why modern framework generations like Spring Boot 3, which require Java 17 and the jakarta namespace, cannot be adopted piecemeal.

Automated tooling helps with the mechanical share of this. Namespace transformers and refactoring recipes can rename javax to jakarta and flag removed APIs across a large codebase far faster than by hand. What they cannot do is decide what the migrated code should become, re-establish behavior the removed modules provided, or prove the result still behaves identically. The rename is scriptable. The judgment and the verification are not.

The slice-by-slice path off legacy Java

We treat a Java estate the way we treat any legacy system: not one risky rewrite, but a sequence of small, reversible steps.

A strangler facade sits in front of the application so the legacy Java app and the modernized path run side by side. We work through the estate slice by slice — a module, a service boundary, a set of endpoints — and before any slice carries live traffic, we prove it behaves identically to the legacy: same outputs, same side effects, validated against real behavior rather than assumptions. AI-accelerated discovery reads the source, the framework configuration, and the integration points end to end and captures what the code actually does — including the undocumented business rules the original authors never wrote down — under senior-engineer review. Traffic shifts only on green, rollback stays a flag away, and the legacy stack is retired only once nothing depends on it. For teams weighing the sequencing, incremental versus big-bang is the trade-off that matters most.

When a JDK upgrade is enough

Not every Java application needs a framework migration, and we’ll say so. A stable, well-contained service under no compliance pressure, whose code is reasonably portable, is often best served by a straightforward JDK upgrade to a current LTS release — the lowest-cost path that clears the end-of-life risk. And a stable WebSphere 8.5 or 9.0 deployment is not an emergency on its own; IBM has stated no planned end-of-support date for those releases. The slice-by-slice approach earns its place when the application is genuinely entangled: when the code is deep in Java EE APIs, when the heavyweight server is a real cost and lock-in problem, or when the system can’t take downtime. Matching the effort to the actual coupling is part of the assessment, not an afterthought.

Where to start

The first step is small and bounded: understand the estate before committing to a path. A discovery call scopes which JDK and framework versions are in play, how much of the code is bound to Java EE and the app server, where the compliance exposure sits, and whether the right move is a runtime upgrade or a deeper migration — on evidence, not a sales pitch. Reach the team at sales@modernlift.ai.

Frequently asked questions

Is Java 8 still supported?
Oracle ended free public updates for JDK 8 in January 2019 and Premier Support in March 2022 (Oracle's Java SE Support Roadmap); patches now require a commercial subscription, with paid Extended Support running to December 2030. Many estates also run unsupported community builds. Either way, Java 8 is a frozen platform — it receives no new features and, without a paid subscription, no security fixes.
What breaks when you upgrade from Java 8 to Java 17 or 21?
More than a recompile, because the JDK itself changed. Java 11 removed the bundled Java EE and CORBA modules (JEP 320) — JAXB, JAX-WS, JTA and CORBA now have to be added back as explicit dependencies. Java 16 and 17 strongly encapsulate JDK internals (JEP 396 and 403), so libraries that reflected into sun.misc.* stop working until upgraded. The Nashorn JavaScript engine was removed in Java 15, applets are gone, and the SecurityManager was deprecated for removal in Java 17. Most of these have clean fixes, but they surface at runtime rather than at compile time, which is why an upgrade needs real testing, not just a build.
Should we target Java 17 or Java 21?
Both are LTS releases with long support horizons, so either clears the end-of-life risk. Java 21 is the newer LTS and the natural target for a fresh modernization, since it carries the latest features and the longest runway. Java 17 is a reasonable interim step when a critical dependency or framework does not yet certify 21. The decision usually comes down to what your framework generation and key libraries officially support, not a preference for the higher number. Pick the newest LTS your stack is actually certified on, and plan to keep moving forward from there.
What is the difference between Java EE and Jakarta EE?
Oracle transferred Java EE to the Eclipse Foundation in 2017, where it became Jakarta EE. Jakarta EE 8 kept the old javax.* package names, but from Jakarta EE 9 (December 2020) the namespace moved to jakarta.* because Oracle retained the javax trademark. The practical consequence — legacy Java EE is a dead end; all forward motion happens under Jakarta EE on the new namespace.
Do we have to leave WebLogic or WebSphere to modernize Java?
Not always. IBM has stated no planned end-of-support date for WebSphere 8.5 and 9.0, and Oracle still supports current WebLogic releases, so a stable app server is not an emergency by itself. The pressure usually comes from the JDK and framework underneath, and from the cost and lock-in of the heavyweight server — which is what an incremental move to a lighter, current stack addresses.
Who provides legacy Java migration services?
ModernLift does. We provide legacy Java migration services for US enterprises — moving applications off frozen JDKs, old Java EE, and heavyweight app servers onto a current, supported stack. The work happens behind a strangler facade, each slice proven to behave identically to the legacy before it carries live traffic — not swapped out in a single rewrite. Scope a Java estate on a [discovery call](/meet) or reach us at sales@modernlift.ai.
How much does a Java migration cost?
There's no list price, because cost tracks the work, not a SKU. The drivers are the size of the estate, how many slices it breaks into, how deeply the code is bound to Java EE APIs and a specific app server, the integration and data complexity, and how much parity proving each slice needs. A straightforward JDK upgrade sits at one end; a framework migration off a heavyweight server sits at the other. The [legacy cost calculator](/legacy-cost-calculator) gives a first-order estimate, and a [discovery call](/meet) turns it into a scoped number.
How do I choose a Java migration partner?
Judge a Java migration partner on how they de-risk the move, not on how fast they promise a rewrite. Look for an incremental, slice-by-slice approach behind a facade, explicit parity proof that each slice behaves identically before cutover, rollback that stays a flag away, and an honest read on whether you even need a full migration versus a runtime upgrade. Our guide to [evaluating application modernization vendors](/modernization-guides/application-modernization-vendors) lays out the questions worth asking.