6 · Modernization options
What you can run, and on which runtime
Modernize offers what your repository qualifies for, and the list behind it is data rather than product code. This section covers where the list comes from and what is on it today, grouped by the runtime it applies to.
The Modernize screen
Modernize turns the analysis into a set of cards. Every card is there because of something found in your source, and it shows you what that was.
On the .NET repository above you get Decompose to microservices,
Govern this API, Security and dependency hardening
and Clean up AI-generated code. Each card has a
detected: chip — C# · 8 endpoints · 6 entities — naming what
qualified it.
The cards say what you would get rather than naming a category. "Derive the true OpenAPI 3.1 contract from the code, close the drift against the published spec, enforce the house rules, and add in-process conformance tests plus CI so the contract can't lie" tells you something. "API governance" does not.
What is ready and what is not are both on the page
Cards further down are marked as coming rather than ready, and say so on their face. Nothing is offered as available and then turns out to be a waiting list.
The playbook library
Behind those cards is the library itself, with the runs grouped by what they apply to.
The cross-runtime ones work on any stack: compare against another repository, consolidate microservices, migrate a database in any dialect, cut deployment cost, move services to serverless, and the test-automation set.
The .NET section is there because this repository is .NET. Other runtimes bring their own.
Each row is tagged with what kind of run it is, so a run that adds new capability is easy to tell apart from one that changes what you already have. Rewrites, which produce a new codebase rather than changing this one, are a third kind and have their own entry point.
How an option gets there
The list is a map file, not product code. The workbench has no built-in knowledge of any particular technology. Everything it can offer is described as data.
A rule is two things
Each option comes with a way to detect it, and each rule has at most two parts: which files to look at, and optionally a pattern their contents have to match. Rules combine as all of these or any of these. That is all there is to it.
Open a project and one scan runs every option's rules. Only the ones whose evidence actually turns up become cards, which is why each card can print the fact that qualified it. Nothing is offered just because a file has a certain extension.
The card is in the map too
An option's name and description sit next to its rules, so the screen can be drawn without loading anything heavy. The run itself is fetched when you click it.
Why there are no totals on this page
Adding an option means changing the map: the patterns, the card, and the run it points at. Installed copies pick that up over the update channel without a new release of the application. What follows is what stands today, and it will grow, so nothing here is counted.
How to read the tables
Options with no marker can be run today. coming means the option shows up in the product as a visible but disabled card, still gated on real evidence from your repository, so you only ever see the ones that would apply to you. in progress means it is being built now.
Offered on every runtime
Some options are offered wherever the runtime is supported, because the problem they solve is not specific to a language.
| Option | What it does |
|---|---|
| Security & dependency hardening | Close the security gaps: dependencies with known advisories, secrets left in config, weak authentication and data-protection defaults, and the injection and exposure problems that apply to this stack. Fixed in blocks you review, with scanning added to CI. |
| Clean up AI-generated code | Find and fix generated slop before it ships: dependencies that do not exist, copy-pasted duplication, dead and unreachable code, placeholder stubs, silent TODOs, and calls to APIs that were never real. Reviewed block by block, with gates so it cannot come back. |
| Uplift the runtime | Move the project onto the current version of its runtime and framework: the language or SDK version, the dependencies, old project formats, and the breaking APIs that come with it. In place, not a rewrite. |
| Set up CI | Build or improve a CI pipeline for this runtime: build, test and coverage, lint, and a security scan, committed as a workflow you review. |
| Set up CD / deploy | Containerize the application and wire up delivery: a multi-stage image, deploy manifests, and a build, scan, push and deploy pipeline, with health checks, metrics and structured logging. |
Security hardening is one option but not one set of checks. It runs whatever tools that runtime has: dependency advisories and injection analysis on the JVM, the npm audit trail and prototype-pollution sinks for JavaScript, module vulnerabilities and insecure patterns for Go, advisory and licence policy for Rust, misconfiguration and over-permissive access for Terraform.
By runtime
Beyond the ones above, each runtime brings the work that is specific to it.
.NET
| Option | What it does |
|---|---|
| Decompose to microservices | Split the monolith into services you can deploy separately, with bounded contexts, a database per service, sagas, and a current .NET and Aspire target. |
| Upgrade .NET to the latest | Move the projects onto the current .NET line, whether they are on .NET Framework or a few versions behind. In place, in blocks you review. |
| Govern this API | Work out the real OpenAPI contract from the code, close the gap against the published spec, enforce your house rules for errors, pagination and status codes, and add conformance tests so it cannot drift again. |
| Move stored procedures to .NET | Port Oracle PL/SQL and SQL Server T-SQL routines into EF Core or Dapper methods, working from the real routine structure and parameter directions rather than a text scan. |
| Prove the ported procedures behave the same | Run a test matrix against the original procedures and record everything they did: output values, returned rows, errors raised, rows written. Then replay the same matrix against the ported C# and fail on any difference. |
Java
| Option | What it does |
|---|---|
| Decompose to microservices | Split the monolith into services you can deploy separately, with a database per service and a current target stack. |
| Upgrade Java to a current LTS | Move onto a current long-term-support Java in place: the compiler level, the build toolchain that has to be current for the build to run at all, and the few places where an API left the JDK. |
| Rewrite legacy Java in modern idioms | Re-express Java 7 and 8 era code as a new codebase next to the untouched original: records, sealed types, pattern matching, the modern date and time API, and the jakarta namespace. Choose the in-place upgrade instead if you want the same code on a newer JDK. |
| Struts / JSF to Spring Boot coming | Retire the old web layer. Actions and managed beans become controllers and services, and the pages move to a modern view layer. |
| Refactoring & code health coming | Restructure, adopt modern idioms, and pay down technical debt. |
| CI/CD pipelines coming | Automate build, test, scan and publish on every change. |
Scala
| Option | What it does |
|---|---|
| Framework upgrade | Move off frameworks that are dead or out of support onto maintained ones. |
| Refactoring & code health | Restructure, adopt modern idioms, and pay down technical debt. |
| Fixes & stabilization | Clear known defects, deprecations and flaky behaviour. |
| Cloud & deployment | Containerize and ship to modern infrastructure. |
| Observability | Structured logs, metrics and traces. |
| CI/CD pipelines | Automate build, test, scan and publish on every change. |
| Governance & compliance | Enforce standards, licensing and policy, with gates that leave an audit trail. |
Node and TypeScript
| Option | What it does |
|---|---|
| Decompose to services | Split the monolith along bounded contexts, taking services out one at a time behind the API you already have. |
Python
| Option | What it does |
|---|---|
| Decompose to microservices | Split the monolith into services you can deploy separately, with a database per service and a current async target stack. |
| Python 2 to 3 coming | Finish the move off Python 2: syntax, renamed standard library, the bytes and string boundary, and a supported set of dependencies, checked module by module. |
Front-end frameworks
| Option | What it does |
|---|---|
| Angular modernization | AngularJS 1.x to modern Angular, or a stepwise update within modern Angular. It works out which of the two applies. |
| Vue 2 to Vue 3 coming | Move off Vue 2, which is end of life, to the Composition API and current tooling, component by component. |
PHP
| Option | What it does |
|---|---|
| Legacy PHP modernization coming | Bring 5.x-era PHP onto the modern line: removed APIs replaced, typed code, Composer packaging, and a supported framework core. |
Go · Rust · C and C++ · Terraform · COBOL
None of these has a modernization option of its own today. Go, Rust, C, C++ and Terraform get everything in Offered on every runtime. For C, C++ and COBOL the real work is a change of language, which is Moving to another language.
Not tied to a runtime
Some work is about the estate rather than the language it is written in.
Across repositories and databases
| Option | What it does |
|---|---|
| Compare against another repository | Read a second repository with the same pass and compare every layer: technology, structure, routes, contracts, data model, dependencies, tests, health and activity. You get what is shared, what differs, which side is stronger where, and whether the path is a migration or a merge. Nothing is changed. You get a report. |
| Migrate database (any dialect) | Move a relational database to a new engine: schema, types, constraints, views, sequences, and stored-procedure logic. The source dialect is read from your own schema and you choose the target. |
| Consolidate microservices coming | Merge services that were split too far back into modules that hold together, keeping separate deployment only where it is worth it. |
Test suites
| Option | What it does |
|---|---|
| Test automation uplift | Move a Selenium suite to a Playwright TypeScript project that runs. Locators are translated into provable equivalents rather than guessed, waits become automatic, and every original test is accounted for so coverage cannot quietly drop. |
| Selenium suite cleanup coming | Steady the suite where it is: replace fixed sleeps with explicit conditions, put the constantly flaky tests aside, and delete dead or duplicate cases. |
| Refactor to Page Object Model coming | Move inline locators into page objects, replace brittle selectors with stable ids and roles, and remove duplicated flows. |
| Coverage expansion coming | Find the user flows the suite never reaches, write cases for them, and show what moved from untested to tested. |
| Generate test data in progress | Turn your declared data model — types, constraints, foreign keys and routine parameters — into datasets you can commit, at whatever level of coverage you choose. |
Platform and operations
| Option | What it does |
|---|---|
| Go cloud-native coming | Containerize, move configuration and secrets out of the code, add health probes and graceful shutdown, make the app stateless so it can scale sideways, then generate the deployment manifests. |
| Go event-driven coming | Put a broker between services that talk to each other directly, with the outbox pattern, consumers that can safely retry, and a dead-letter queue. |
| Add distributed tracing coming | Instrument the application with OpenTelemetry: traces that cross service boundaries, the standard metrics, and exemplars. "It is slow somewhere" becomes a flame graph. |
| Upgrade to structured logs coming | Replace ad-hoc printing with structured, levelled logs that carry trace and request ids, hide secrets, and go somewhere you can query. |
| Cut cloud cost coming | Measure the real workload, set the right resource requests and limits, add autoscaling, slim the image, and give you a before-and-after estimate. |
| Move services to serverless coming | Find the services that spike and then sit idle, and re-platform them so they scale to zero. |
| Make it AI-ready coming | Turn the application into something an agent can operate: an MCP server over its domain and APIs, a retrieval store over the code, docs and data model, and a defined set of tools an assistant is allowed to call. |
Moving to another language
A rewrite is its own thing, not a modernization option, because it produces a new codebase instead of changing the one you have. The original is left alone, and what the new system is built from is the structure the analysis recovered, not a model's reading of the old source.
From the mainframe
| Option | What it does |
|---|---|
| COBOL to modern Java | Programs, copybooks, job control and screens mapped onto a modern layered service, with the parsed structure as the source of truth. |
| COBOL to C# / .NET | The same path, targeting modern .NET. |
| COBOL to Python coming | The same path, targeting modern Python. |
| COBOL to Go coming | The same path, targeting Go, for small static binaries and batch throughput. |
| RPG (IBM i) to Java coming | AS/400-era RPG programs, with display files, subfiles and DB2 access mapped onto a modern layered service. |
| PL/I to Java coming | Procedures, includes and data structures recovered by the parser and rebuilt on a modern stack. |
From older application stacks
| Option | What it does |
|---|---|
| Legacy Java to modern Java | A full rebuild of the application, starting from the structure the analysis recovered. |
| Legacy Java to Kotlin coming | The same, in idiomatic Kotlin, with null safety and coroutines on the modern JVM. |
| VB6 to modern .NET coming | Forms, modules and COM dependencies listed first, then rebuilt as a desktop or web application you can maintain. |
| Delphi to modern .NET coming | Units, forms and data modules listed and rebuilt. |
| Perl to Python coming | Scripts and their dependencies listed, and the behaviour pinned down before anything is converted. |
From systems languages
| Option | What it does |
|---|---|
| C to Rust · Go · modern Java coming | Rebuild the C system, working from the structure recovered from the source. Rust for memory safety without losing the systems shape, Go for simple concurrency and static binaries, Java for a layered service. |
| C++ to Rust · Go · modern Java coming | The same three targets from C++, with ownership replacing manual lifetime handling where the target is Rust. |
From older front ends
| Option | What it does |
|---|---|
| AngularJS to React | Rebuild the 1.x application as modern React, component by component, working from the routes and controllers the analysis found. |
| jQuery front end to React coming | Pages and behaviours recovered from the DOM-manipulation code, and rebuilt with state where the selectors used to be. |
Rewrite or upgrade
Where both exist for the same stack they answer different questions. An upgrade keeps your codebase and moves it forward: same modules, same behaviour, a diff you can review. A rewrite gives you a new codebase next to the old one. Java has both, and the wording on each option says which is which.
CogniDev