Java EE to Spring Boot & Jakarta EE
Moving from Java EE to Spring Boot or Jakarta EE means leaving the frozen javax.* namespace for a maintained framework generation. Oracle handed Java EE to the Eclipse Foundation in 2017, and from Jakarta EE 9 (December 2020) the package namespace changed from javax.* to jakarta.* (Eclipse Foundation), so all forward motion now happens on the new namespace. The two viable targets are Jakarta EE on a modern server, or Spring Boot — whose version 3 baselines Java 17 and jakarta.* (spring.io, 2022). Either way, the move is best done incrementally, one slice proven equivalent before it carries live traffic.
Part 2 got the runtime current. This part takes the layer the runtime upgrade keeps colliding with: the framework. The Java EE APIs a legacy application is written against — servlets, EJB, JPA, JMS, CDI — and the javax.* namespace they live in are frozen. A modern JDK runs them, but they no longer move, and the ecosystem is steadily leaving them behind. Crossing onto a maintained framework is the move that reconnects the application to a living platform.
The namespace break, and why it is non-negotiable
The single fact that shapes this whole migration is a package rename. Oracle transferred Java EE to the Eclipse Foundation in 2017, where it became Jakarta EE. Jakarta EE 8 kept the familiar javax.* package names, but from Jakarta EE 9 (December 2020) the namespace moved to jakarta.* — because Oracle retained the javax trademark (Eclipse Foundation).
That sounds like a cosmetic rename. In a real codebase it is not. javax.servlet, javax.persistence, javax.ejb and the rest become jakarta.servlet, jakarta.persistence, jakarta.ejb — and the change reaches every import, every configuration reference, and every dependency that itself imports those packages. You cannot half-cross the line: a class is on one namespace or the other, and the libraries it depends on must match. This is mechanical work, but it is pervasive, and it is the reason “move to Jakarta EE” is a project rather than a find-and-replace.
The strategic point underneath the mechanics: legacy javax.* Java EE is a dead namespace. Security patches, modern libraries, and tooling increasingly target jakarta.* only. Staying put does not freeze the application in a stable state — it slowly disconnects it from the ecosystem it depends on for fixes.
Two destinations: Jakarta EE or Spring Boot
There are two viable targets, and the right one depends on where the application already leans — not on which is fashionable.
Jakarta EE on a modern runtime keeps the application in the EE programming model — container-managed transactions, CDI, JPA, the standard APIs — but on the current jakarta.* namespace and a maintained server (a lightweight one like Open Liberty rather than a heavyweight traditional server). It is the more direct path when the application is built deeply around container-managed services and you want continuity rather than a paradigm shift.
Spring Boot is the de facto enterprise default and the more common destination. It offers a broader ecosystem, a lighter and more container-friendly deployment model, and an opinionated structure that suits CI/CD. The critical version fact: Spring Boot 3 (November 2022) baselines Java 17 and the jakarta.* namespace, on Spring Framework 6 (spring.io, 2022). So a move to current Spring Boot is a move to the new namespace and a modern JDK — which is exactly why Part 2’s runtime upgrade comes first. Note also that Spring Boot 2.x reached end of open-source support on 30 June 2023 (spring.io), so “we’re already on Spring Boot” is not the same as “we’re current.”
Neither target is universally correct. An application thick with EJB and container services may reach Jakarta EE more cheaply; one already leaning on Spring may find Spring Boot the natural home. The decision belongs in the assessment, weighed on the code’s actual coupling.
Doing it incrementally — not all at once
The wrong way to cross the namespace line is the big-bang way: branch the whole codebase, rewrite every import, change every dependency, and attempt one cutover. On a large application that is a months-long branch that drifts from production, can’t be partially shipped, and lands as a single high-risk event — the big-bang rewrite this series argues against throughout.
The incremental way puts a strangler facade in front of the application and moves one slice at a time:
- Pick a slice — a bounded set of endpoints or a cohesive service — small enough to reason about end to end.
- Rebuild it on the target framework, on the
jakarta.*namespace and a current JDK, with its own clean dependencies. - Prove parity. Before the slice carries live traffic, prove it behaves identically to the Java EE original — same outputs, same persistence behavior, same edge cases — often via shadow traffic through both paths.
- Shift traffic gradually, watching telemetry, with rollback one config change away.
- Retire the slice from the legacy framework. Repeat.
Where the legacy and modernized slices must talk to each other across the namespace boundary during the transition, an anti-corruption layer keeps the old model from leaking into the new one. The bridge code is temporary by design — built to be thrown away once the slice is fully across.
The reversal worth keeping: you do not migrate the framework, you migrate slices of the application onto a new framework — and the namespace break, pervasive as it is, becomes a sequence of small, proven moves rather than one irreversible rewrite.
What makes this genuinely hard
Two parts of the framework resist the slice-by-slice treatment more than the rest, and they are worth flagging now because the next part is devoted to them. EJB ties business logic to container-managed transactions, pooling, and lifecycle that a modern framework handles differently — lifting that logic out means replacing the container’s guarantees, not just renaming an import. And shared persistence means a slice’s JPA entities are often joined to data the rest of the monolith still owns, so the framework move runs into the data-decomposition problem this series treats separately. Naming these early keeps the migration honest: most of a Java EE app crosses the line mechanically, but the EJB and data seams are where the real engineering lives.
Not every app needs to cross this line
Not every Java EE application needs to leave for Spring Boot, and a clean Jakarta EE move is sometimes all that is warranted — or even just staying on a supported Jakarta EE 8 while the bigger questions wait. The framework migration earns its cost when the application is cut off from security patches and modern libraries by the dead javax.* namespace, when the team can’t hire for the old stack, or when the framework generation blocks the runtime upgrade the security posture demands. Where the application is stable, contained, and under no such pressure, the lower-cost runtime upgrade from Part 2 may be the whole job. Matching the depth to the pressure is the assessment’s work.
Where this leads
Most of a Java EE application crosses the namespace line as mechanical work. Two things don’t: the EJB session and entity beans that bind business logic to the container, and the old Struts web tier that often sits in front — the latter carrying a security history that makes it urgent in its own right. Part 4, Migrating Off EJB and Struts, takes the hardest parts of the old framework: what EJB’s container guarantees actually provided, why Struts is a liability as well as a dead end, and how to lift both out a slice at a time.
Frequently asked questions
- 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 (Eclipse Foundation). The practical consequence is that legacy javax.* Java EE is a dead end — it no longer evolves — and all maintained, forward-looking work happens under Jakarta EE on the new namespace.
- Should we move to Spring Boot or Jakarta EE?
- Both are valid; the choice turns on where the application already leans. If it is built around container-managed Java EE services and you want continuity, Jakarta EE on a modern runtime is the more direct path. If you want the broader ecosystem, lighter deployment, and the de facto enterprise default, Spring Boot is the common destination — its version 3 baselines Java 17 and the jakarta.* namespace (spring.io, 2022). Neither is universally right; the honest answer depends on the code's existing coupling, not fashion.
- Can you migrate from Java EE to Spring Boot without a big-bang rewrite?
- Yes, and it is the safer way. A facade routes traffic to the legacy Java EE application and the new Spring Boot or Jakarta EE path side by side, and you move one slice — a set of endpoints, a bounded service — at a time. Each slice is proven to behave identically to the legacy before it carries live traffic, traffic shifts only on green, and rollback stays a flag away. The legacy framework is retired slice by slice, never in a single risky cutover.