A software portfolio made of monolithic applications, manual releases, and servers sized for peak traffic slows down every change and drives up maintenance costs. Redesigning it according to cloud-native principles, meaning containers, decoupled services, and automated release pipelines, reduces both problems. Let’s see when application modernization is worth it and which strategies to use to tackle it.
Drupal 7 reached end of life on January 5, 2025, CentOS 7 on June 30, 2024, and PHP 7.4 back in November 2022. Organizations still running applications on these stacks have two options: pay third parties for extended support or remain exposed to vulnerabilities that will never be patched again. Moving them as-is to a cloud VM does not solve the problem; it only moves it to a different bill. To get the elasticity and release velocity from the cloud that justified adopting it in the first place, it is very often necessary to proceed with application modernization. This term refers to refactoring and updating an enterprise application portfolio to ensure applications are performant, functional, and secure on cloud infrastructures, rather than simply hosting them there.
In this article, we take a deep dive into application modernization, analyzing its benefits and possible implementation approaches.
What is meant by application modernization
By application modernization (also called legacy modernization), we mean updating existing software to align it with modern development approaches in terms of languages, frameworks, and infrastructure. Code changes, regardless of their depth, are aimed at optimizing performance and functionality within the new architecture, which today is almost inevitably in the cloud (modern applications that remain on-premises or at the edge due to data residency requirements, sovereign cloud choices, or hybrid and private cloud architectures still adopt cloud-native patterns, typically on Kubernetes).
This journey must be approached case by case, depending on the type of applications, and gradually. To run properly on cloud infrastructure with tangible results, not all traditional applications need massive overhauls: in some cases, a few targeted changes are enough. Furthermore, the move to the cloud must happen seamlessly, without disrupting operations or service continuity: it is therefore better to proceed step by step, first handling migration and then carrying out optimization work.
Whatever the industry, postponing the modernization of legacy applications has measurable costs. An unsupported framework, such as AngularJS after December 2021 or PHP 7.4 after November 2022, no longer receives security patches. Every maintenance task requires increasingly rare and expensive skills, and a trivial change remains stuck in the monolith’s release cycle for months. Breaking down the application into services that scale, update, and are secured independently makes it possible to shift from quarterly releases to weekly or daily releases for individual services.
Why modernize legacy applications
Designed to run in typically client-server environments, traditional applications are developed and executed using a monolithic approach, as a single unit containing all functions and tightly coupling related processes. Any error or code change directly affects the behavior of the entire application. Consequently, every intervention requires extensive testing, a full software update, and a new deployment; the result is, in many cases, service downtime for an extended period.

In a monolithic application, changing a framework, database, or runtime is possible, but it usually requires modifying the entire codebase and deploying it in a single block, which leads to it being postponed. A monolith also has limited scalability: to handle a traffic spike on a single feature, the entire application must be replicated. Every new feature touches the same codebase and lengthens test and release cycles. Finally, development often remains locked into a single vendor or team.
On the cost front, the comparison with a modern architecture is not straightforward. Costs tied to an on-premises monolith (licenses, hardware, maintenance, staff dedicated to patching) can decrease after migration. However, savings only materialize if cloud consumption is governed with resource rightsizing and cost allocation per service. Without this discipline, the cloud bill can rise, as we explain in the section dedicated to cloud costs and FinOps.
An application that requires a downtime window for every release bundles changes into quarterly packages: even a security fix waits for the next maintenance window alongside everything else. When the underlying stack reaches end of life, like CentOS 7 in June 2024 or Drupal 7 in January 2025, that waiting turns into exposure. Two alternatives remain: pay third parties for extended support, a recurring fee that keeps the application alive without making it any faster to change, or go without patches. What is the real cost, then, of leaving the monolith as it is?
Moving an existing application portfolio toward a cloud-native model removes specific technical constraints. A monolith that requires a maintenance window for every release, or that scales only by adding hardware, makes every new business requirement slow and expensive to deliver.
Application modernization allows organizations to drive innovation to the benefit of the business while minimizing risks: completely replacing or rebuilding a working application that supports multiple core processes can be risky or prohibitively expensive. Modernization, instead, proceeds incrementally, keeping errors under control and spreading investment costs over time.
Finally, application modernization enables a full transition to the cloud: in many cases, if legacy solutions can run in the cloud, the underlying on-premises infrastructure can be migrated, delivering cost optimization and greater flexibility. This specific approach is called rehosting and represents the first step of modernization, as we will see in a few paragraphs.
Why the cloud bill can rise after modernization
Savings only materialize if consumption is actively governed. A rehosting migration that copies virtual machines 1:1 from the data center brings servers sized for annual peaks into the cloud, running 24/7 and billed on consumption rather than amortized. Shifting to microservices introduces a second effect: a single application becomes dozens of services, each with its own replicas, databases, load balancers, and test environments. Individual line items are small, but the total surprises anyone who built their business case purely on VM costs.
| Cost item | Why it grows after modernization | FinOps practice |
|---|---|---|
| Compute | VMs migrated with on-premise sizing, Kubernetes requests (the CPU and memory each pod reserves on the node) inflated “just in case” that lock up almost empty nodes | Rightsizing based on actual metrics, Horizontal Pod Autoscaler (adds or removes replicas based on load), cluster autoscaler or Karpenter (automatic node provisioning) |
| Non-production environments | Dev, staging, and preview environments for each service stay on at night and on weekends | Scheduled shutdowns, ephemeral environments with automatic expiration |
| Network traffic | Inter-service calls across different availability zones, egress to the Internet or other providers | Topology-aware routing (routes calls to instances in the same zone), egress monitoring per service |
| Managed services | One database per microservice multiplies minimum paid instances | Shared instances in non-production environments, serverless tiers where workloads are intermittent |
| Observability | Verbose logs, high-cardinality metrics (too many label combinations), tracing on 100% of requests | Tiered retention, trace sampling, ingestion filtering |
| Spending commitments | Reserved instances or savings plans (discounts in exchange for multi-year spending commitments) purchased before workloads stabilize | Purchasing only after establishing several months of measured baseline |
The underlying issue is allocation: without knowing which team and service drive each dollar, no one has an incentive to optimize. That is why, from the very first migration, we apply a mandatory tagging and labeling convention (team, service, environment, cost center), validated in CI. In Kubernetes clusters, we use tools like OpenCost or Kubecost to allocate the cost of shared nodes to individual namespaces. This forms the basis for showback to teams and, once organizational maturity allows, chargeback.
The cycle proposed by the FinOps Foundation (Inform, Optimize, Operate)1 works when the key metric is unit cost rather than total spend. The cost per order, per active user, or per page served can legitimately rise in absolute terms as the business grows. But if the cost per transaction increases as well, something in the architecture or sizing needs revisiting. Budget alerts per service and a monthly review with developers and Operations close the loop: the engineers writing code see the financial impact of their decisions, such as a retry loop without backoff or a query downloading gigabytes across regions.
How do you modernize legacy applications, from microservices to DevOps?
While the benefits of application modernization are clear, the associated topics, necessary expertise, and methodologies to execute it properly are less obvious.
Looking deeper into the architectural aspect, modern applications differ from monolithic solutions through their microservices structure: each application is conceived and built as a collection of independent functional units that can be added, removed, or modified individually without affecting the overall behavior of the system. Microservices are owned by specialized teams that can work autonomously to implement changes within their scope without causing downtime or outages.
Within modular architectures, using APIs becomes essential to foster integration and interoperability among different services, accelerating development timelines when adding new capabilities.
At the infrastructure level, modern applications leverage the advantages of containerization: essentially, applications or microservices run inside self-contained runtime environments (containers), ensuring consistent execution regardless of the underlying hardware and guaranteeing full software portability across hybrid and multi-cloud environments. Thanks to dedicated orchestration platforms (such as Kubernetes, to name the most well-known open-source solution), containers can be managed dynamically and automatically, overcoming the inevitable rise in complexity.
From a process standpoint, fully capitalizing on cloud-native applications requires adopting Agile methodologies and DevOps practices. Agile methodologies break the development process down into sprints (work blocks), following an iterative and incremental approach. Each cycle aims to deliver an increment (a software improvement such as a bug fix or a new feature), which is released and immediately tested by users to gather feedback. Work proceeds step by step until the final release, allowing course corrections along the way and ensuring alignment with business goals.
DevOps practices, on the other hand, promote collaboration between development and Operations teams, reducing application deployment times and automating software lifecycle management.
With the pillars of the cloud-native approach defined, the next step is deciding what to do with each legacy application. The framework we adopt is the 7 Rs model, which defines seven strategies. Retire decommissions applications that no longer deliver business value. Retain temporarily keeps the system where it is because migration is not cost-effective or not yet feasible. Relocate moves workloads to another infrastructure without changing the application or OS, for instance from on-premises VMware to VMware Cloud. Repurchase replaces the software with an equivalent SaaS product. Rehost migrates the application as-is using a lift-and-shift approach. Replatform introduces targeted adjustments to take advantage of managed services. Refactor redesigns the application using cloud-native patterns.
In a real portfolio, all seven strategies coexist: the same organization might retire an obsolete ERP module, retain a mainframe, and rewrite the e-commerce frontend. Retire, Retain, Relocate, and Repurchase do not require touching application code. The three Rs that involve technical changes to the application are the following, and should be chosen based on the specific case, budget, and goals:
rehosting (often called Lift & Shift): moves the application to the cloud with little to no code changes. Along with Relocate, it is the least expensive option in the short term, but the application remains monolithic and does not leverage the provider’s managed services;
replatforming: adapts the application to the cloud with targeted changes that introduce modern development techniques, without altering its core architecture;
refactoring: rewrites the application, partially or entirely, using cloud-native frameworks, often decomposing it into microservices: it is the most intensive intervention and the only one that unlocks the full benefits of the cloud.
The full picture, showing effort, code impact, risks, and expected ROI for each of the seven strategies, is as follows:
| Strategy | Effort | Impact on code | Main risks | ROI |
|---|---|---|---|---|
| Relocate | Low: hypervisor-level move, for example with VMware Cloud on AWS | None: VMs are moved without changes to OS or application | Dependency on the VMware stack and its licensing costs, no access to native cloud services | Fast: rapid data center exit, but the operating model remains on-premises |
| Rehost | Low: weeks per application | None or minimal, application remains monolithic | Higher cloud bill than on-premises without rightsizing, technical debt unchanged | Fast but limited: data center exit and hardware contract termination |
| Replatform | Medium | Targeted adjustments: containerization, externalized configurations, databases and queues replaced with managed services | Version incompatibilities on databases and drivers, regressions not covered by tests | Medium: less operational overhead on patching, backups, and scaling |
| Refactor | High: months per module, years for an entire monolith | Partial or complete rewrite toward microservices | Project duration, shared database separation, feature freeze if done as a “big bang” | Highest in the long term: independent releases and scalability per service |
| Repurchase | Low on development, high on data and process migration | Code is replaced by a SaaS product | Vendor lock-in, loss of custom workflows, historical data migration | Eliminates software maintenance, but introduces recurring subscription costs |
| Retire | Low | The application is decommissioned | Hidden dependencies (batch jobs, point-to-point integrations), data retention requirements | Immediate: licenses and infrastructure freed up |
| Retain | Zero in the short term | None: application stays on-premises due to compliance constraints or approaching end of lifecycle | Ongoing data center costs, risk of becoming permanent without periodic reviews | No direct benefit: decision must be re-evaluated regularly, such as at hardware or contract renewals |
These three paths are not mutually exclusive: in practice, an application often goes through them sequentially. It begins with rehosting to exit the data center, moves to replatforming when databases and message queues are swapped for managed cloud services, and reaches refactoring only for modules that change frequently and suffer the most from the monolith’s constraints. What should be avoided is the “big bang”: rewriting everything in parallel and cutting over in a single weekend. In projects we have overseen, full rewrites take years, freeze new feature development throughout the process, and reach production when requirements have already shifted.
Incremental refactoring centers on the Strangler Fig, the pattern described by Martin Fowler in 2004. The name comes from the strangler fig, which grows around a host tree until it completely replaces it. In software, the setup works like this: a facade, typically a reverse proxy or an API gateway, is placed in front of the monolith to intercept all traffic. Initially, the facade routes every request to the monolith. As a capability is rewritten as an independent service, the facade routes only the relevant traffic to the new component. The monolith gradually loses features one by one and is decommissioned once it receives no more traffic. The system stays in production continuously, and any step can be rolled back simply by routing traffic back to the old code.
By itself, however, the pattern is not enough. The bottleneck is rarely the code: it is the shared database, which remains connected to both the monolith and the new services in the diagram. Around the facade, other techniques must be combined, each solving a specific problem.
| Technique | Problem it solves | When to use it |
|---|---|---|
| Anti-corruption layer | Prevents legacy data models from contaminating new services | When the new service needs to read from or write to the monolith during the transition |
| Branch by abstraction | Replaces an internal component without long-lived feature branches | When the feature to be extracted cannot be reached via the facade and is only invoked through internal calls |
| Parallel run | Verifies that the new service produces identical outputs to the old one | For critical business logic (pricing, billing), comparing outputs before cutting over |
| Feature flags and canary releases | Routes traffic by percentage or user segment, with immediate rollback capabilities | At every routing cutover on the facade |
| Change data capture | Keeps the legacy database synchronized with the new service’s datastore | When separating a table from the monolithic database without interrupting writes |
A practical constraint is worth highlighting: every step must be reversible within minutes, otherwise the incremental nature is merely an illusion. If extracting a service requires an irreversible data migration, the proper sequence is: first synchronize the datastore with change data capture, then cut over reads, then cut over writes, and only then drop the table from the monolith.
Where AI accelerates refactoring and where it does not
In legacy refactoring, the slow part is understanding old code, not writing new code. Business rules are buried in thousands of lines of COBOL or 5,000-line Java classes, lacking documentation and with the original authors long gone. In this phase, generative models and code intelligence tools make the most tangible impact. An LLM with full repository context explains what a batch job does, traces module call graphs, and identifies which tables a transaction updates. Previously, this required weeks of manual analysis and interviews with the few team members left.
| Activity | What AI does well | Where human review is required | Example tools |
|---|---|---|---|
| Code understanding | Summarizes programs and modules, extracts business rules, documents implicit dependencies | Extracted rules must be validated by domain experts: the model describes code, not intent | Full-repo context LLM assistants, IBM watsonx Code Assistant for Z for COBOL |
| Characterization testing | Generates unit tests capturing existing behavior, including edge cases | Verifying that tests check actual outputs rather than merely mimicking the implementation | GitHub Copilot, Amazon Q Developer |
| Technology upgrades | Updates Java and framework versions, replaces deprecated APIs | Diff reviews and full CI builds | OpenRewrite (deterministic recipes), Amazon Q Developer transformation |
| Service boundary identification | Suggests bounded contexts by analyzing static coupling and runtime traces | The final decision depends on teams, shared data, and change frequency, not just class graphs | vFunction, static analysis combined with distributed tracing |
| Language conversion | Translates COBOL or PL/I to Java while preserving logic | Numeric semantics, error handling, performance | LLM-assisted translators, always followed by parallel runs |
Language conversion requires the greatest caution. A line-by-line COBOL-to-Java translation produces code that compiles but remains procedural, retaining the structure of the original monolith. This is known as “JOBOL”: the language changes, but the architecture does not. There is also a more subtle risk. COBOL operates on fixed-point decimal arithmetic (COMP-3 fields), whereas a translation using double instead of BigDecimal introduces rounding errors on monetary amounts. These discrepancies only surface under production volumes. For this reason, the parallel run technique described earlier remains mandatory even when code is model-generated.
Which tool should be used, and for what? For large-scale mechanical changes, such as moving from Java 8 to 17 or Spring Boot 2 to 3, we prefer deterministic recipes like OpenRewrite. They produce reproducible results on every run and generate reviewable diffs. Generative models handle edge cases that recipes miss and assist with code understanding, where output variability is acceptable. The operational sequence follows the same logic as the Strangler Fig: first generate and review characterization tests, then refactor. This gives every change, whether human or automated, a measurable safety net.
Finally, there is a governance requirement to address before getting started. Legacy code contains proprietary business logic and, frequently, hardcoded credentials or real test data. Before connecting an AI assistant to a repository, organizations must know where code is sent. It is also essential to know whether the vendor retains or trains on it, and whether the contract guarantees zero retention or requires self-hosting the model in a private environment.
12Factor, best practices for modern application development
To simplify application modernization and guide cloud-native software development, the 12Factor Methodology outlines best practices that optimize application behavior in the cloud.
The official document outlines the core goals of the methodology, geared toward designing applications that:
are reproducible across environments because they explicitly declare and isolate all dependencies (Factor II, Dependencies) and keep development, staging, and production as similar as possible (Factor X, Dev/prod parity). This serves as the cure for the classic “it worked in test” dilemma. Published in 2011 by Adam Wiggins, then at Heroku, the methodology predates widespread container adoption, but teams applying these principles find their applications can be containerized effortlessly;
strictly separate codebase from configuration (Factor III, Config): credentials, endpoints, and environment-specific parameters live in environment variables, not in versioned properties files or hardcoded values. In legacy applications, the opposite is common, meaning changing a database requires recompiling and redeploying;
scale horizontally by adding identical processes because they adopt a stateless model (Factor VI, Processes): no process relies on local memory or filesystem state between requests, and sessions and file uploads are handled by backing services like Redis or object storage. In typical monoliths, sticky sessions and local disk uploads are precisely what prevent adding a second node;
start up and shut down in seconds (Factor IX, Disposability): they handle graceful shutdown cleanly and restart without manual procedures. Frequent deploys and autoscaling require this, and an application server taking minutes to start up cannot guarantee it;
treat logs as event streams written to stdout (Factor XI, Logs) and leave log aggregation and routing to observability tools in the hands of the infrastructure, rather than relying on rotated server files inspected over SSH. Together with the other factors, this brings Development and Operations teams closer together and makes continuous integration and continuous delivery pipelines sustainable.
Following the 12 principles for modern application design ensures a solid foundation for modernizing an enterprise application portfolio, cutting IT management costs while delivering expected business advantages, including strategic agility, operational continuity, and faster service delivery.
Organizing modernization and required skills
Modernizing applications requires five core skill sets: cloud-native development (microservices, containers, APIs), production Kubernetes management, DevOps practices with automated CI/CD pipelines, change management to guide teams through the new operating model, and FinOps to govern cloud costs. These should be organized into cross-functional teams; skills missing in-house can be supplemented with an external partner.

Compared to a “big bang” rewrite, incremental modernization mitigates risk because the application remains in production and every step is reversible, as in the Strangler Fig pattern. However, it requires precise technological and methodological knowledge, from designing module boundaries to maintaining a release pipeline solid enough to move from quarterly to weekly deployments.
First, teams must have strong command of cloud-native development techniques, with particular emphasis on microservices architectures, API design, containers, and orchestration platforms. In addition, solid skills in Agile methodologies and DevOps practices are required, accompanied by effective change management capabilities.
Adopting modern software lifecycle frameworks changes daily work in measurable ways: teams transition from quarterly releases to weekly or daily releases per service, driven by multidisciplinary teams working in parallel on shared objectives. To sustain this pace, developers gather feedback directly from end users and coordinate closely with Operations, ensuring each release informs the next.
Proven experience in similar projects makes or breaks an application modernization initiative. Through legacy portfolio analysis, teams must be able to assign one of the 7 Rs (retire, retain, rehost, relocate, replatform, repurchase, refactor) to each application and prioritize tasks based on dependencies, business value, and risk. The outcome should be a roadmap that evolves the portfolio in waves: first addressing applications that free up resources or unblock others, followed by those requiring deep refactoring to leverage cloud-native services.
The success of an application modernization project requires technical skills, strategic vision, and past experience, but today it also hinges on a phased delivery model. It begins with application discovery, which is increasingly automated. Next comes the assessment of legacy code, dependencies, and technical debt, detailed in the “How a modernization assessment is structured” table below. This forms the foundation for defining the business case using FinOps practices to govern cloud costs from the initial migration, while AI-assisted refactoring accelerates legacy code analysis and rewriting. Partnering with proven partners who have already transitioned legacy workloads to Kubernetes flattens the internal learning curve during the first migration and allows characterization tests and platform foundations to be built in parallel.
Who executes the project matters as much as the strategy chosen: the same migration presents different risks and timelines if performed by a team already familiar with the legacy codebase versus one that must first reconstruct its logic. The diagram below illustrates the criteria we use to determine, case by case, when to engage external legacy application modernization services and when the in-house team has sufficient skills and capacity to proceed independently.
How a modernization assessment is structured
A thorough assessment does not start with the target technology, but with an inventory of existing assets. In our engagements, the actual scope is almost always broader than the client estimates: forgotten applications, unmonitored batch jobs running on neglected servers, undocumented point-to-point integrations. For this reason, the first phase relies on automated discovery tools that analyze code, dependencies, and network traffic before conducting team interviews.
| Phase | Main activities | Output |
|---|---|---|
| Discovery | Automated code scanning, dependency mapping, inventory of databases and integrations | Application catalog with actual usage data |
| Architectural assessment | Monolith analysis, bounded context identification, technical debt measurement (test coverage, unsupported frameworks, degree of coupling) | Complexity and risk score for each application |
| Business case and TCO | Comparison between current costs (licensing, hardware, maintenance) and estimated cloud costs, using FinOps practices to govern consumption | 3-5 year TCO estimate and prioritized backlog |
| Roadmap | Assignment of one of the 7 Rs to each application and release scheduling | Phased rollout plan with measurable milestones |
For applications slated for refactoring, the roadmap rarely calls for a total rewrite in a single deployment. Instead, it follows the incremental Strangler Fig pattern discussed earlier, with the assessment determining extraction order. Where should you begin? With the module that has the fewest dependencies and the highest deployment frequency, because that is where the monolith incurs the highest costs in downtime and testing. An initial service with minimal coupling to the rest of the codebase also allows testing facades, CI/CD pipelines, and observability tooling without endangering core application functions.
Real-world success stories in application modernization
Weighing the opportunities and challenges of application modernization, it is no surprise that many companies have already embarked on successful paths to update their software portfolios.
Many digital natives dismantled their monoliths long before cloud-native had a name. Twitter, now X, began moving away from Ruby in 2009 and completed the shift in April 2011, announcing it was running on Blender, a Java-based server2. Netflix followed a similar journey on the infrastructure side: in January 2016, it completed the migration of its services from proprietary data centers to AWS. But modernization is not limited to tech giants. In Italy, established enterprises in traditional industries have also led successful initiatives.
For example, pharmaceutical company Zambon completed a replatforming and redesign project for its digital ecosystem (corporate, international, and product portals), successfully establishing a multi-cloud infrastructure.
An example of migration going beyond simple rehosting is ilGiornale.it: the publication entrusted SparkFabrik with its cloud governance and the cloud-native evolution of its CMS platform. This project exemplifies refactoring and re-architecting strategies: the CMS was redesigned to leverage cloud services natively rather than being moved as-is onto new machines.
Where do you start modernizing legacy applications?
The first concrete step is an assessment of your application portfolio. Build an inventory using automated dependency discovery, assign one of the 7 Rs to each application, and establish priorities based on three measurable criteria: maintenance cost, exposure to vulnerabilities, and release cycle duration.
An unsupported framework, for instance, forces a decision between paying for extended support or living without future security patches.
Weighing that cost against the required migration effort immediately reveals which application to tackle first.
If you want to plan this work with us, SparkFabrik’s Application Modernization service begins with this assessment. To align these operational decisions with your broader IT strategy, consult our CIO guide to cloud native.
Notes and sources
The cycle proposed by the FinOps Foundation (Inform, Optimize, Operate): “FinOps is performed by working iteratively on the Framework Capabilities through three phases: Inform, Optimize and Operate.” (source: https://www.finops.org/framework/phases/) ↩︎
(Twitter, now X, began decomposing its Ruby on Rails monolith into JVM services starting in 2010), f…: “In 2009, they began the complex process of switching from Ruby to JavaScript. This switch was finally completed in April 2011, when the company announced they were now operating from a Java server called Blender.” (source: https://www.bairesdev.com/blog/twitter-tech-stack/) ↩︎




