System Design
June 8, 2026

Modernizing Java Applications with the Five Rs and Strangler Pattern

Most architecture work is not greenfield. Many Java teams inherit existing applications, databases, user interfaces, integrations, operational habits, and business constraints. Modernization is the discipline of improving that landscape without pretending it can be redesigned from a blank page.

Two useful tools are the five Rs and the strangler pattern. The five Rs help decide what to do with each component. The strangler pattern helps replace one application gradually by routing selected features to a new implementation while the old system continues running.

The Problem

Brown-field projects are full of constraints.

Existing system:
Old code
Existing database
Legacy integrations
Known users
Production traffic
Operational processes
Limited test coverage
Business deadlines

The target architecture cannot be chosen only by preference. The team must decide what to keep, what to improve, what to rewrite, and what to replace.

A complete rewrite can be attractive, but it is risky. A lift-and-shift migration can be fast, but it may not solve maintainability. A gradual migration can reduce risk, but it requires careful routing, testing, data handling, and governance.

The Five Rs

The five Rs provide a portfolio-level way to classify modernization choices.

Rehost:
Move the application to newer infrastructure with minimal changes.

Refactor:
Move to a PaaS-style environment with limited packaging and configuration changes.

Revise:
Make small code changes to benefit from modern infrastructure.

Rebuild:
Rewrite the application using a new architecture and framework.

Replace:
Discard the application and use another product, SaaS, or no replacement.

Rehost

Rehost is commonly described as lift and shift. The application moves to different hardware, a virtualized environment, or cloud infrastructure, but the architecture and code stay mostly the same.

This can reduce infrastructure cost and migration risk, but it rarely fixes old design problems.

Refactor

In this chapter context, refactor means moving the application toward a PaaS environment while avoiding architecture and code changes. The team may introduce release automation, autoscaling support, or self-healing capabilities from the platform.

The benefit is limited effort. The downside is that the original code remains hard to evolve.

Revise

Revise keeps most of the application but changes selected areas such as session handling, persistence, or interfaces. The goal is to gain some benefit from the newer platform without rebuilding everything.

This is often a practical middle ground for Java systems that cannot be rewritten yet.

Rebuild

Rebuild means implementing the application again using a new architecture, likely microservices or a related cloud-native style.

This gives the team the most control and can create the most future benefit, but the effort, cost, and risk are high.

Replace

Replace means retiring the application or using an existing product or SaaS solution instead. This is related to the buy side of the build-versus-buy decision.

Replacement can lower long-term maintenance, but it usually gives the team less control and may require business processes to adapt to the new product.

Choosing a Strategy

A practical way to think about the five Rs is effort versus benefit.

Lower effort, lower transformation:
Rehost -> Refactor -> Revise

Higher effort, higher transformation:
Rebuild -> Replace

The right choice can differ per component. A reporting module might be replaced by an existing product. A critical payment capability might be rebuilt. A stable but low-value internal tool might be rehosted.

Do not force one strategy across an entire enterprise landscape unless the business and technical context really support it.

The Strangler Pattern

The strangler pattern works at application level. A new implementation grows alongside the old one and gradually takes over selected features.

The pattern relies on controlling ingress: the point where users or clients enter the system.

Initial state:
All traffic -> Legacy application

Intermediate state:
Feature A -> New service
Feature B -> Legacy application
Feature C -> Legacy application

Final state:
All traffic -> New implementation
Legacy application retired

This is easier when features are already exposed through APIs. A routing layer, load balancer, or API manager can direct selected calls to the new implementation.

If calls are internal method calls or older protocols that are hard to redirect, the team may need an adapter layer. The adapter translates legacy calls into network APIs such as REST or SOAP.

Another option is routing in the client layer through feature flags. This can be more fine-grained, but it also makes the frontend more complex and more invasive to change.

Picking the First Feature

Start with a feature that is isolated and self-contained, but not meaningless.

A trivial feature will not test the new stack. A highly critical feature may create too much risk. The best first feature is simple enough to migrate safely and important enough to reveal real integration, deployment, testing, and monitoring issues.

Domain boundaries help here. A bounded context can give the team a natural feature boundary for migration.

A practical rollout can use canary or A/B style routing when supported by the available routing layer. If the new feature fails, traffic can move back to the legacy implementation because the old feature still exists.

Modernization Workflow

A controlled modernization can follow this sequence:

1. Inventory the existing application and integrations.
2. Classify each component with one of the five Rs.
3. Identify ingress points and routing options.
4. Add tests around existing behavior.
5. Pick a first bounded feature.
6. Build the new implementation beside the old one.
7. Route a small amount of traffic to the new implementation.
8. Monitor behavior and compare outcomes.
9. Expand traffic and migrate more features.
10. Retire the legacy implementation after a safe grace period.

The process can be parallelized, but it must be governed. Everyone should know which feature is implemented where at any point in time.

Testing Comes First

Testing is one of the most important modernization concerns. If the old system has poor test coverage, the team may not know whether the new implementation preserves the required behavior.

Before rewriting or redirecting features, invest in automated tests around the legacy behavior. These tests become a safety net for the migration.

The goal is not to freeze every bug as required behavior. The goal is to understand what the system currently does, then decide intentionally what should remain, change, or disappear.

Data Consistency

Modernization often changes the data layer. A new service may use a dedicated data store, a modified schema, or a different persistence technology.

The cleanest approach is to move integration from the data layer to the application layer by exposing APIs. When that is not possible, data integration techniques such as change data capture may be used to move changes between old and new systems.

The risk is inconsistency. During migration, old and new applications may need to exchange data. Every flow should define the source of truth and how reconciliation works.

Session Handling

During a strangler migration, users may move between old and new implementations. Session data and security state must survive that movement.

A practical solution is to externalize session handling into a shared store that both old and new applications can use.

Legacy application -> shared session store <- new service

Keeping two separate session systems manually synchronized is possible, but it is harder to maintain and more error-prone.

Troubleshooting and Endpoints

Troubleshooting becomes harder during modernization because a single user journey may cross old code, new services, adapters, and routing layers.

A unique identifier per call helps correlate logs across subsystems.

Endpoint management also needs planning. APIs and user interfaces may change. External clients, end users, and partner systems need communication about rollout schedules and compatibility periods. Keeping an older API version available for a limited time can reduce migration shock.

Common Mistakes

The first mistake is starting modernization before test coverage is strong enough. That turns migration into guesswork.

The second mistake is ignoring data ownership. If old and new systems write related data without a clear strategy, inconsistencies will appear.

The third mistake is forgetting sessions. Users do not care that a migration is in progress. They expect one coherent experience.

The fourth mistake is letting the feature map become undocumented. During the strangler migration, the team must know which implementation owns each feature.

The fifth mistake is choosing Rebuild or Replace only because they sound cleaner. High benefit usually comes with high risk and high cost.

Checklist

  • The existing landscape has been inventoried.
  • Each component has a chosen R strategy.
  • Business value justifies the modernization effort.
  • Legacy behavior has automated test coverage.
  • Ingress points and routing mechanisms are understood.
  • The first migrated feature has a clear boundary.
  • Data ownership and synchronization are designed.
  • Session handling works across old and new implementations.
  • Logs include correlation identifiers.
  • Endpoint changes are communicated before rollout.
  • Training is planned for technical staff and impacted users.
  • The legacy system has a clear retirement path.

Conclusion

Modernization is a sequence of tradeoffs. Rehost, refactor, revise, rebuild, and replace give teams a vocabulary for choosing the level of change. The strangler pattern gives a practical way to replace an application gradually.

For Java teams, the safest path is usually incremental: add tests, control routing, migrate one meaningful feature, observe production behavior, and repeat. The goal is not only to use newer infrastructure. The goal is to reduce legacy risk while creating a system that is easier to operate, evolve, and understand.

Share:

Comments0

Home Profile Menu Sidebar
Top