Skip to content

Modern-Legacy Platforms Decoder

FieldValue
TypeSkill Resource
Source~/.copilot/skills/architecture/references/discovery/modern-legacy-platforms.md
DescriptionNot specified

Source Content

Modern-Legacy Platforms Decoder

A field guide for reading the non-mainframe legacy stacks a modernization team actually meets — end-of-life .NET, Java EE, VB6, Oracle Forms, classic PHP, and stored-procedure-heavy databases. Read this before phase 2 when the platform family is “modern legacy”: not COBOL or RPG, but still a legacy-modernization problem.

Contents

The discovery method here is identical to the mainframe and IBM i path — inventory, then data model, then business rules, then dependencies, then tests, then docs, then a scored strategy — and the strategy catalog is identical too (see modernization-strategy.md; its R’s are platform-agnostic). What differs is only how you read the code. These stacks are closer to modern languages, so the decoding is lighter: you rarely fight EBCDIC, packed decimal, or the RPG cycle.

The traps are different instead — framework lock-in, code-behind sprawl, business logic buried in stored procedures, and dead framework versions that no longer receive security patches. That last one turns a discovery into a clock: an app on an unsupported runtime is a live risk, not just technical debt.

.NET Framework, ASP.NET Web Forms, and classic ASP

Microsoft’s server-side web stacks span three eras, and a single agency site often mixes them. Recognize which era you are in before you read a line of logic.

What it is

Three generations sit under the “ASP.NET” name:

  • Classic ASP (pre-2002) — VBScript or JScript inlined in .asp files, no compilation, no framework. The oldest and rawest.
  • ASP.NET Web Forms (2002–) — a stateful, event-driven model on top of HTTP: drag-drop server controls, a page lifecycle, and ViewState that pretends the web is a desktop form. Files come in pairs.
  • ASP.NET MVC / Web API (2009–) — the controller/action model, closer to how modern frameworks read. Legacy but far more tractable.

Alongside these, WCF (Windows Communication Foundation) is the SOAP/service layer, and the older ASMX web service is its predecessor.

How to recognize it

File markers give the era away:

  • .aspx + .aspx.cs (or .aspx.vb) pairs → Web Forms. The .aspx is markup; the code-behind (.aspx.cs) is the logic.
  • .asp with <% … %> script blocks and no code-behind → classic ASP.
  • .asmx → an ASMX web service; .svc → a WCF service.
  • Global.asax → application lifecycle events (Application_Start, Session_Start).
  • web.config → the config file: connection strings, app settings, HTTP modules, and the target framework version.
  • Web.Debug.config / Web.Release.config → config transforms per build.

The version is the risk signal. .NET Framework 4.8 is the last of the Windows-only line and is in extended support only — no new features, security fixes on a shrinking runway. Anything below 4.6.2 is out of support entirely [VERIFY] — treat it as a security clock. The modern successor is .NET (Core) 8+, cross-platform and unrelated in runtime, so moving is a re-platform, not a version bump.

Where the business logic hides

Web Forms scatters logic in ways that make it hard to find:

  • Code-behind event handlersButton1_Click, Page_Load, GridView1_RowDataBound. The real rules live in these handlers, wired to UI controls by name, not called from a service. This is code-behind sprawl: business logic welded to the page.
  • Page_Load with if (!IsPostBack) — the branch that runs only on first load versus every postback. Initialization and rule-firing split across that boundary.
  • ViewState — hidden serialized state posted back with every form. Values you cannot see in the markup may be driving decisions.
  • Inline <% %> scriptlets in classic ASP — HTML, SQL, and logic mixed in one file, the same anti-pattern as classic PHP below.
  • web.config — feature flags, connection strings, and behavior toggles that change what the code does without touching code.

Top discovery gotchas

  • VB.NET vs C#. Both compile to the same IL and appear in the same solution. Read .vb code-behind with VB syntax (Dim, End Sub, no semicolons); do not assume C#.
  • The page lifecycle is invisible. InitLoad → control events → PreRenderUnload fire in an order the code never states. A handler’s effect depends on when in that sequence it runs.
  • Session and Application state hold data across requests in server memory — hard to trace and hostile to horizontal scaling.
  • Server controls generate their own SQL/data access via SqlDataSource in the .aspx markup — data access hiding in the view, not the code-behind.

Typical modernization path

Web Forms has no forward-compatible path in .NET 8 — the component model was not ported [VERIFY], so any move is a rewrite of the presentation layer. Strategies that usually fit:

  • New Frontend, Same Backend — put a modern SPA or Blazor front over the existing data and services while the backend is untangled.
  • Strangler Fig — route pages one at a time to a new ASP.NET Core app behind a shared URL, retiring .aspx pages as they are replaced.
  • Rehost / Replatform for the runtime if logic is stable but the Windows Server underneath is end-of-life.

End-of-life Java and Java EE (J2EE)

“Legacy Java” is a wide band: from a 2004 Struts 1 app on WebLogic to a 2014 Spring 3 service on Tomcat. The frameworks, not the language, decide how hard it reads.

What it is

The stack is a Java web application, usually built for an application server, using one or more of these now-dated frameworks:

  • Servlets and JSP — the base layer: HttpServlet classes and .jsp template pages. Everything else sits on top.
  • Struts 1 — an early MVC framework built on Action classes and struts-config.xml. End of life and a known security-CVE magnet [VERIFY].
  • EJB 2 (Enterprise JavaBeans) — heavyweight components with home/remote interfaces and reams of XML. Rules hide inside session beans.
  • Old Spring (2.x/3.x) — XML-heavy bean wiring in applicationContext.xml, before annotations took over.
  • Applets — client-side Java in the browser, long dead; a running one is a hard blocker.

How to recognize it

Look at the build, the server, and the framework config:

  • pom.xml (Maven) or build.xml (Ant) → the build. Ant is the older signal.
  • web.xml in WEB-INF/ → the servlet deployment descriptor; it maps URLs to servlets and filters.
  • struts-config.xml → Struts 1. applicationContext.xml → Spring. ejb-jar.xml → EJB.
  • .ear (enterprise archive) or weblogic.xml / ibm-web-bnd.xml → deployed to WebLogic or WebSphere, the two heavyweight commercial app servers.
  • Java 6, 7, or 8 as the target. Java 8 is still widely supported but is the floor of “modern”; 6 and 7 are long out of public support [VERIFY].

Where the business logic hides

  • Struts Action classes — the execute() method of each Action is a request handler; the rules for that URL live there.
  • EJB session beans — a @Stateless bean (or an EJB2 SessionBean implementation) holds transactional business logic behind a remote interface.
  • JSP scriptlets<% … %> blocks with Java, SQL, and HTML mixed in the page. The same one-file soup as classic ASP and classic PHP; treat every scriptlet as a place logic can hide.
  • Service and DAO layers — better-organized apps put rules in *Service classes and data access in *DAO classes; follow those names first.

Top discovery gotchas

  • XML is a second source of truth. Struts URL routing, Spring bean wiring, and EJB transaction attributes live in XML, not Java. Read the config alongside the code or you miss half the behavior.
  • App-server APIs leak in. JNDI lookups, WebLogic/WebSphere-specific classes, and container-managed transactions tie the app to its server; those calls do not run on plain Tomcat.
  • Reflection and dependency injection hide the call graph — the wiring is in XML or annotations, so “who calls this” is not answerable by grep alone.
  • Two Java 8 sub-eras. Pre- and post-lambda code reads very differently; do not assume streams and lambdas in an older Java 8 codebase.

Typical modernization path

The language survives; the frameworks and app server usually do not. Common strategies:

  • Strangler Fig — stand up a Spring Boot service and migrate endpoints off Struts/EJB one at a time behind a shared gateway.
  • Refactor — when the domain logic is sound, lift it out of Action/scriptlet code into services, then move off the commercial app server to Spring Boot on a plain container.
  • Rehost / Replatform — a stable app on an end-of-life WebLogic license can move to Tomcat or a container as a first, cheap step.

VB6 / classic Visual Basic and COM

Visual Basic 6 is a desktop application platform from 1998. It is the trap that looks easy — the code reads like plain English — but cannot be built on any modern toolchain.

What it is

A VB6 project is a Windows desktop app (or a COM component) built from forms and modules:

  • Forms (.frm) — a window plus its event-handler code, both in one file.
  • Modules (.bas) — shared procedures and global variables, no UI.
  • Class modules (.cls) — object definitions, often exposed as COM objects.
  • Project files (.vbp) — list the files and references; .vbg groups multiple projects.

Data access is usually DAO or ADO (older RDO shows up too), and the app leans on COM / ActiveX / OCX controls — third-party UI widgets registered on the machine.

How to recognize it

  • .frm, .bas, .cls, .vbp, .vbg files → a VB6 project. Not to be confused with .vb (VB.NET, a different language).
  • .ocx / .dll references in the .vbp → COM/ActiveX dependencies.
  • Begin VB.Form … End blocks at the top of a .frm → the serialized form layout, followed by the event-handler code.
  • Option Explicit, Dim … As, Sub/Function/End Sub, Set obj = … → VB6 syntax.

Where the business logic hides

  • Form event handlersCommand1_Click, Form_Load, Text1_Change. As with Web Forms, logic is welded to UI controls; the rules are in the .frm, not a separate layer.
  • .bas modules — shared calculations and global state; the closest thing to a service layer.
  • .cls COM classes — reusable business objects, sometimes shared with other apps through COM.
  • Embedded SQL — DAO/ADO code builds SQL strings inline, so the data model and its rules are stitched through the event handlers.

Top discovery gotchas

  • It is unbuildable on modern Windows. The VB6 IDE and runtime are unsupported, 32-bit only, and the ActiveX/OCX controls it depends on are frequently missing or unregisterable on a current OS. You often cannot compile it to confirm behavior — you read it cold.
  • COM registration is invisible in the source. A control referenced by GUID must be registered on the machine; the source does not tell you where the .ocx came from or whether it still exists.
  • VB6 ≠ VBA ≠ VB.NET. VBA (in Excel/Access) shares syntax but lives inside Office; VB.NET is a .NET language. Do not conflate the three.
  • Undeclared variables — without Option Explicit, a typo silently creates a new Variant. Behavior can hinge on defaults you cannot see.

Typical modernization path

VB6 has no in-place upgrade — the language is a dead end. Strategies that fit:

  • Rebuild — a small VB6 utility is often cheapest to rewrite outright from the discovered rules, since the code is compact and the platform is gone.
  • Replace with COTS — many VB6 apps encode a generic function (inventory, scheduling) that a modern product now covers.
  • Rehost — as a stopgap only, run it in a locked-down Windows VM to buy time; it removes no risk, it just contains it.

Oracle Forms & Reports and PL/SQL

Oracle Forms is the “database-as-application” pattern in its purest form: the UI, the logic, and the data all live in and around one Oracle database. Reading it means reading the database as much as the app.

What it is

An Oracle Forms application is a set of screens generated by the Oracle Forms runtime, tightly bound to an Oracle database:

  • Forms (.fmb source, .fmx compiled) — a screen with blocks (each mapped to a table), items (fields), and triggers (event code in PL/SQL).
  • Reports (.rdf) — the Oracle Reports output definitions.
  • Menus (.mmb / .mmx) — the application menu structure.
  • PL/SQL libraries (.pll / .plx) — shared PL/SQL attached to forms.

The real weight is in the PL/SQL inside the database: packages, procedures, functions, and triggers on tables.

How to recognize it

  • .fmb/.fmx, .rdf, .mmb/.mmx, .pll/.plx files → Oracle Forms & Reports.
  • .pks / .pkb (package spec / body), .sql, .prc, .fnc, .trg → PL/SQL source.
  • CREATE OR REPLACE PACKAGE, CREATE OR REPLACE TRIGGER, BEGIN … END;, DBMS_OUTPUT → PL/SQL.
  • A dependency on an Oracle Application Server / Forms & Reports Services runtime → the deployment model.

Where the business logic hides

The logic is mostly in the database, not the form:

  • Table triggersBEFORE INSERT/AFTER UPDATE triggers fire rules on every data change, invisibly from the app’s point of view. Harvest every trigger.
  • PL/SQL packages — the true service layer; a package like PKG_CLAIMS holds the procedures that do the work.
  • Form-level triggersWHEN-VALIDATE-ITEM, WHEN-BUTTON-PRESSED, POST-QUERY — validation and navigation logic attached to the screen.
  • Views — a database view can encode joins and filters that are effectively business rules.

Top discovery gotchas

  • .fmb is a binary file. You cannot grep it directly; it must be converted to a text representation (.fmt) with Oracle’s own converter to read the triggers [VERIFY]. Plan for that step in the inventory.
  • The database is the application. More logic may live in triggers and packages than in the forms. Get a database connection and read ALL_SOURCE, ALL_TRIGGERS, and ALL_DEPENDENCIES — the catalog is authoritative, the way DB2 DDL is on the mainframe.
  • Trigger cascades — one trigger updates a table whose trigger updates another. Order and re-entrancy are hard to see and easy to break in a rewrite.
  • Forms runtime version — Forms 6i, 10g, 11g, and 12c differ; older runtimes are out of support and pin the app to a specific, aging Oracle stack.

Typical modernization path

The forms are the disposable part; the PL/SQL is the asset (or the liability). Strategies:

  • New Frontend, Same Backend — replace the Forms UI with a modern web app while the PL/SQL packages keep running, at least at first.
  • Refactor — lift stable business logic out of triggers into an application service layer, so the rules stop being invisible.
  • Replace with COTS — where the Forms app is a generic ERP/finance function that a packaged product now covers.

Classic PHP and other scripting legacy

The scripting-language legacy is defined by one anti-pattern above all: HTML, SQL, and business logic mixed in a single file, with no framework and no layers.

What it is

The common case is PHP 5 (or earlier) with no framework, or an old MVC framework past its support:

  • No-framework PHP.php files that emit HTML, query the database, and decide business rules in one script.
  • Old CodeIgniter (2.x) or CakePHP (1.x/2.x) — early PHP MVC frameworks, now several major versions behind and unsupported [VERIFY].

PHP 5 reached end of life at the end of 2018 [VERIFY]; a PHP 5 app is on an unsupported runtime and is a security clock, like .NET Framework below its supported floor.

How to recognize it

  • .php, .php3, .phtml, .inc files → PHP. mysql_query( (the removed non-i API) → PHP 5-era code [VERIFY].
  • system/ + application/ folders → CodeIgniter. cake/ + app/ → CakePHP.
  • <?php … ?> blocks interleaved with raw HTML → the no-framework, mixed-concern style.

Where the business logic hides

  • Inline in the page — a single .php file often runs the query, applies the rule, and prints the HTML. There is no layer to look in; the logic is wherever the output is.
  • include/require chains — shared logic pulled in by file path, so the call graph is a web of includes rather than function calls.
  • Framework controllers — in CodeIgniter/CakePHP, the controllers/ and models/ folders; better than no-framework but still often thin over inline SQL.

Other scripting and 4GL legacy (brief mentions)

Three more platforms show up occasionally, each a full family in its own right:

  • ColdFusion.cfm/.cfc files using CFML tags (<cfquery>, <cfset>); tag-based logic and SQL mixed into the page.
  • Perl / CGI.pl/.cgi scripts, often the oldest web tier in an organization; logic and DBI database calls inline.
  • Delphi and PowerBuilder — Windows desktop 4GL tools (Delphi’s .pas/.dfm; PowerBuilder’s .pbl libraries and DataWindows). Like VB6, they bind UI, logic, and data access together and are hard to build on modern toolchains.

Top discovery gotchas

  • No separation of concerns. You cannot inventory “the model” or “the service layer” — there may not be one. Inventory by file that handles a URL instead.
  • SQL injection everywhere. Old PHP concatenates user input into SQL. It is both a rule-mining signal (the query shows the data model) and a security finding to log.
  • Register-globals-era assumptions — very old PHP relied on request variables auto-becoming globals, a behavior long removed. Old code may assume it.

Typical modernization path

  • Rebuild — a small no-framework PHP app is often cheapest to rewrite on a modern stack, since there is little structure to preserve.
  • Refactor then Strangler Fig — for a larger CodeIgniter/CakePHP app, introduce a modern framework and migrate route by route.
  • Rehost — only as a bridge; running end-of-life PHP behind a WAF contains risk without removing it.

Legacy data stores where the logic lives in the database

On these stacks the database is not a passive store — it is where a large share of the business logic runs. Miss the database and you miss the system, the same lesson as Oracle Forms above.

What it is

Three data-store situations recur:

  • SQL Server 2000 / 2005 — end-of-life Microsoft SQL Server versions, still running under many .NET Framework and classic ASP apps.
  • MS Access / Jet — an .mdb/.accdb file acting as both database and application (forms, queries, and VBA macros in one file).
  • Stored-procedure-heavy databases — any database where the app is a thin caller and the rules live in stored procedures, triggers, and views.

How to recognize it

  • .mdb / .accdb files → MS Access / Jet.
  • .sql files full of CREATE PROCEDURE, CREATE TRIGGER, CREATE VIEW → a stored-procedure-heavy design.
  • Application code that calls procedures by name (EXEC usp_CalculateBenefit) and does little else → the logic is in the database, not the app.
  • SQL Server compatibility level 80 (2000) or 90 (2005) → an end-of-life engine even if the host was upgraded [VERIFY].

Where the business logic hides

  • Stored procedures — the calculations, validations, and multi-step transactions. A procedure named for a business action (usp_ApproveClaim) is a rule catalog entry waiting to be written.
  • Triggers — rules that fire on insert/update/delete, invisible to the calling app.
  • Views — encoded joins and filters that are effectively rules.
  • Access VBA and macros — in an Access .mdb, logic lives in form-event VBA and query definitions inside the file itself.

Top discovery gotchas

  • The app under-represents the system. If most logic is in T-SQL or PL/SQL, reading only the application code gives you a fraction of the rules. Read the database catalog (sys.procedures, INFORMATION_SCHEMA.ROUTINES) as a first-class source.
  • Access “databases” are single-user file shares in disguise. An .mdb on a network share is a concurrency and corruption risk, and its VBA is easy to overlook.
  • Dynamic SQL inside procedures — a procedure that builds and EXECs a string hides its real behavior from static reading; note these for closer inspection.
  • Linked servers and cross-database queries — a procedure may reach into another database entirely, an integration edge with no network call to trace.

Typical modernization path

  • Data-first migration — because the logic is in the data tier, the schema and procedures move first; see the data-first guidance in modernization-strategy.md.
  • Refactor — lift rules out of stored procedures into an application service layer when they need to change or be tested independently.
  • Rehost / Replatform the engine itself (SQL Server 2000 → a supported version, Access → a real RDBMS) as an early, contained step.

File-extension cheat sheet

Classify by extension first, then confirm by reading the header or the first statements:

Extension(s)It is a…Platform family
.aspx, .ascx, .masterASP.NET Web Forms page / control / master page.NET Framework
.aspx.cs, .aspx.vbWeb Forms code-behind (C# / VB.NET).NET Framework
.aspClassic ASP (inline VBScript/JScript).NET Framework
.asmxASMX web service.NET Framework
.svcWCF service.NET Framework
.cs, .vbC# / VB.NET source.NET Framework or .NET (Core)
web.config, Global.asaxASP.NET config / app lifecycle.NET Framework
.jsp, .jspxJavaServer Pages templateJava EE
.javaJava source (Servlet, Action, EJB, service)Java EE
web.xml, struts-config.xml, applicationContext.xml, ejb-jar.xmlDeployment / framework configJava EE
.ear, .warEnterprise / web archive (app-server deploy)Java EE
pom.xml, build.xmlMaven / Ant buildJava EE
.frm, .bas, .cls, .vbp, .vbgVB6 form / module / class / project / groupVB6
.ocxActiveX / COM controlVB6
.fmb, .fmxOracle Form (source / compiled)Oracle Forms
.rdfOracle ReportOracle Forms
.mmb, .mmxOracle menuOracle Forms
.pll, .plxOracle Forms PL/SQL libraryOracle Forms
.pks, .pkb, .prc, .fnc, .trgPL/SQL package / procedure / function / triggerOracle Forms
.php, .php3, .phtml, .incPHP scriptClassic PHP
.cfm, .cfcColdFusion page / componentScripting legacy
.pl, .cgiPerl / CGI scriptScripting legacy
.pas, .dfm, .pblDelphi source/form / PowerBuilder libraryScripting/4GL legacy
.mdb, .accdbMS Access / Jet databaseLegacy data store
.sqlSQL DDL/DML, stored procedures, triggersLegacy data store

When an extension is missing or wrong, the first lines usually decide it: a <%@ Page %> directive → ASP.NET; Begin VB.Form → VB6; CREATE OR REPLACE PACKAGE → PL/SQL; <?php → PHP; import javax.servlet or extends HttpServlet → Java EE.