This is the second in a series of articles exploring the realities of modernisation. In this article, you’ll learn about:
- The Modern Java Breakpoint: Why the shift from
javaxtojakartacreates an unfixable compatibility break for Struts 1 on modern containers like Tomcat 10 and Jetty 11. - The External Pressure Cooker: How platform baselines, security policies, and cloud migrations force the issue, even if the application code itself hasn’t changed.
- Why “Shims” Fail: A look at why attempting to patch compatibility with shims is not a viable long-term solution for production environments.
- Safe Operation Strategies: What it practically means to run Struts securely on older containers without exposing the rest of your infrastructure to risk.
- The Path to Modernisation: How to use incremental migration patterns and extended support to modernise on your own terms, avoiding risky “big-bang” rewrites.
Table of Contents
- When an Upgrade Isn’t Really an Upgrade
- The Namespace Shift That Changed Everything
- Where Struts 1 and Modern Containers Diverge
- The Breaking Point Usually Isn’t Technical
- Why There’s No “Compatibility Layer” That Fixes This
- Living Safely on the Older Side of the Divide
- Modernisation Doesn’t Have to Be Sudden
- Older Software and Modern Nightmares
- Buying Time Is Sometimes the Smartest Move
- The Cliff Is Real, But It Doesn’t Have to Be Steep

When an Upgrade Isn’t Really an Upgrade
In engineering teams, upgrading infrastructure is supposed to feel routine. Treat your system like cattle, not pets, remember? Updating a JVM, rolling out a new Tomcat image, aligning with the company’s Java baseline: normal steps in keeping a platform healthy. But the moment a Struts 1 application enters the conversation, the word “upgrade” suddenly ceases to mean what people expect.
Engineers try to move an application from Tomcat 9 to Tomcat 10. Or they push for consistency across the estate by adopting Jetty 11. Or they want to standardise on Java 21 because the rest of the organisation already has. And then the build breaks. The server refuses to start. The logs fill with errors about missing classes. What seemed like a straightforward platform refresh becomes a multi-month discussion.
The problem isn’t a misconfiguration. It’s not a library version clash. It’s not even something you can debug. It’s a deeper compatibility break that sits beneath Struts 1 itself.
The Namespace Shift That Changed Everything
For nearly two decades, enterprise Java lived in the javax.* namespace. Every servlet, every filter, every listener, every part of the web stack assumed that convention. When Oracle handed Java EE over to the Eclipse Foundation, the specification could move… but the namespace couldn’t. Legally, javax.* stayed where it was. The new home for enterprise Java needed a new name. It became jakarta.*.
This looks like a branding change. But in practice, it’s a line in the sand. When the containers ( Tomcat, Jetty, Undertow, and the full Jakarta EE servers ) adopted the new namespace, they removed the old one. And as soon as they did, a considerable portion of the Java ecosystem had to decide whether to evolve or remain forever pinned to older platforms.
Struts 1 never had the chance to make that decision. It had already reached its end of life years earlier. No maintainers. No updates. No migration plan. Its entire understanding of the web tier is tied to javax.servlet.*, and that dependency is now stranded on the far side of a one-way transition.
The EE train moved on, and Struts 1 stayed at the station.
Where Struts 1 and Modern Containers Diverge
This is why running Struts 1 on Tomcat 10 or Jetty 11 doesn’t fail gracefully. It often fails absolutely. The container loads, but the framework doesn’t. The classes it depends on have vanished. There is nothing to map requests to. No way to load an ActionServlet. The framework is looking for an API that no longer exists.
It isn’t because Tomcat “removed support for older code.” It’s because the industry had to choose a single namespace for the future, and the old one couldn’t be carried over. With that choice made, legacy frameworks are effectively frozen in place.
And that freeze becomes an architectural constraint. You can’t move the server forward without leaving the application behind. You can’t rely on the security, TLS, memory, or performance improvements that modern runtimes offer. Even when the Struts application itself is stable, the platform underneath it grows older by the year, and at some point, organisations begin to feel the tension.
The Breaking Point Usually Isn’t Technical
The teams that first feel this are rarely the developers. It’s usually platform engineering or cloud migration teams who encounter the incompatibility when they try to consolidate the estate. They realise that every other service can run in a modern container except this one. Or security notices that an approved JDK baseline can’t be applied because one application is locked to an older servlet stack. Or operations teams discover that a vendor they rely on no longer supports pre-Jakarta containers.
Most organisations don’t hit the Jakarta Cliff because Struts stops working. They hit it because the environment around Struts keeps moving forward.
And at some point, “keeping things as they are” stops being a neutral choice and becomes an obstacle.
Why There’s No “Compatibility Layer” That Fixes This
It’s natural to ask whether there’s some form of compatibility layer: a shim, a transformer, a bytecode trick, that could translate old servlet calls into new ones. A few experiments exist, most created by curious engineers rather than production environments. They work up to a point, but they introduce as many risks as they solve.
Bridging two namespaces at the heart of the servlet lifecycle isn’t like adapting a library call one layer up. It touches the core of how request processing works. It affects classloading, security, session handling, filters, and dispatching: everything Struts depends on. And when you begin to modify that profoundly, you aren’t extending Struts anymore. You’re replacing vast chunks of what made it Struts in the first place.
Living Safely on the Older Side of the Divide
This doesn’t mean organisations need to panic. Struts 1 can still run reliably on the platforms it expects. Many businesses today are effectively running a “dual-speed” architecture: modern apps on modern containers, legacy apps on carefully maintained older ones.
But the longer this pattern continues, the more these systems become outliers. Their infrastructure takes special handling. Their patching cycles diverge. Their operational requirements grow distinct. Eventually, it becomes clear that the Struts application is no longer merely old: it has become different in kind from everything else the organisation runs.
At this point, the question isn’t “how do we keep running Struts?” It’s “how do we transition without disrupting the business?”
Modernisation Doesn’t Have to Be Sudden
There is a temptation to frame modernisation as a binary moment: today you’re running Struts; tomorrow you’re running Spring Boot; the day after that you’re cloud native. But real engineering work doesn’t follow that rhythm. Systems evolve in layers, not leaps.
You can wrap Struts inside a Spring Boot application. You can route legacy URLs through one servlet while gradually introducing new controllers alongside it. You can build a new login path while keeping the old one intact during the transition. These approaches allow change without jeopardising uptime, but they require deliberate engineering. They are a bridge, not a toggle.
This is the nuance that organisations often need to hear: modernisation is possible, but not instant. And that time: the months or years it takes to replace what matters most, is usually the part that’s most difficult to secure.
Older Software and Modern Nightmares
Do you have time to execute an accelerated rewrite? So often, the driver for change is an out-of-the-blue moment that leaves you suddenly exposed. A new high-severity CVE in an unsupported old library. While that’s always a bad dream, with more scrutiny on compliance than ever before, those dreams are now nightmares. Forcing upgrades (if you can), rewriting parts of the application to avoid the compromised library, and arguing how to disable the pesky functionality – oh and let’s have another firewall in there somewhere.
When you’re faced with no time to act, you behave accordingly and attempt things you know are unwise and often downright foolish.
Buying Time Is Sometimes the Smartest Move
What you need is time, and that is often available (for a price). Getting time to plan, time to migrate safely, time to let the application time out, and just time to avoid hasty decisions that introduce more risk than they remove.
Extended support from 3rd parties isn’t an admission of defeat; it’s a recognition that legacy systems carry real business value, and that modernisation should happen on the organisation’s schedule rather than being forced by the platform’s constraints.
A Struts 1 application may be approaching retirement, but it should retire on your terms, not because an infrastructure upgrade forced your hand.
The Cliff Is Real, But It Doesn’t Have to Be Steep
Struts 1 belongs to a different era of Java, and the Jakarta transition made that undeniable. But the presence of this cliff doesn’t mean organisations have to leap blindly or freeze forever. It means they need clarity. They need a realistic plan. They need the breathing room to execute it safely.
The purpose of this article is not to amplify urgency, but to explain why modern containers and Struts 1 can no longer share the same road and why that is a technical fact, not a strategic failure.
In the following article, we’ll move from platform concerns to the framework’s internals themselves. Struts 1 introduced architectural patterns that shaped a generation of Java developers, but those patterns don’t map cleanly onto the assumptions of modern frameworks.
Understanding that mismatch is essential to knowing how to migrate without losing the business logic that still matters.

Interested in Learning More?
Steve Poole is a speaker at JCON! This article explores the challenges legacy Struts applications face when moving to modern Java and Jakarta EE platforms. His JCON session, “Engineering a Modern Java Platform”, takes a broader look at Java modernization, covering the tools, migration strategies, and platform changes that organizations need to navigate when upgrading long-lived applications. If you can’t attend live, you can explore many JCON sessions online – definitely worth checking out!

This article is part of the JAVAPRO magazine issue:
Engineering Intelligence – Building Systems in the AI Age
Explore what engineering means in the AI age — beyond code generation and automation.
Gain insights into performance-critical Java systems, evolving architectures, and the responsibilities that come with building intelligent software.
Discover the edition →