v1.0

Migration Engine — User Guide

Complete reference for migrating COBOL, Fortran, Classic ASP and PHP legacy systems to modern Java with behavioral equivalence guarantees.

COBOLFortranClassic ASPPHP 4/5Java 17+Spring Boot 3

Version: 1.0 · Engine branch: feature/autonomous-legacy-code
Supported targets: Java 17+, Spring Boot 3.x, Jakarta Servlet 6.x


Table of Contents

  1. Introduction & System Overview
  2. Supported Language Matrix
  3. Key Concepts & The Emulation Layer
  4. Configuration & Customization Guide
  5. Troubleshooting & Edge Cases

1. Introduction & System Overview

What the Engine Does

The Migration Engine is a source-to-source translation system that converts legacy mainframe and web-tier programs — written in COBOL, Fortran, Classic ASP, PHP 4/5, and related languages — into modern, production-ready Java code.

It does more than a text substitution. For every input program it:

  1. Detects the execution context (batch/CLI vs. web template) before generating a single line of Java.
  2. Preserves behavioral equivalence — the translated Java must produce identical outputs for identical inputs.
  3. Upgrades the architectural tier — a COBOL sequential file becomes a buffered byte stream; a Classic ASP page becomes a Spring Boot @Controller; a Fortran GOTO loop becomes an idiomatic while or do-while construct.
  4. Emits a runtime emulation layer (Section A) containing helper classes that faithfully reproduce legacy type semantics (fixed-width strings, 1-indexed tables, packed-decimal arithmetic).

Core Philosophy

PrincipleWhat it means in practice
Behavioral equivalenceThe Java output produces the same results as the legacy source for all valid inputs, including edge cases like COBOL out-of-range ODO indices or Fortran computed GOTO with out-of-range selectors.
Architectural tier upgradeLegacy patterns are mapped to their modern structural equivalent — not just their syntactic one. A web page becomes a controller, not a main() method.
Precision over convenienceFinancial fields (COMP-3, PIC 9(n)V9(m)) are always emitted as BigDecimal, never double or float.
Fail loudOut-of-bounds table access and binary buffer overflows throw detailed exceptions rather than silently returning garbage data.
Zero stubsThe engine never emits // TODO, placeholder methods, or unimplemented blocks. Every line of the source is translated.

2. Supported Language Matrix

2.1 COBOL

Source ConstructJava OutputNotes
PIC X(n)CobolString(n)Fixed-width, space-padded, truncating
PIC 99 / PIC 9(n)CobolInteger(initial, digits)Max-value enforced (e.g. PIC 99 → max 99)
PIC 9(n)V9(m)CobolDecimal(initial, scale)BigDecimal-backed; HALF_EVEN rounding
COMP-3 / packed decimalCobolDecimal + CobolComp3ReaderByte-stream unpacking; never text splitting
OCCURS n TO m DEPENDING ON vCobolTable<T>(min, max, dependingOn)ODO variable reference; 1-based access
PERFORM VARYING v FROM s BY i UNTIL cfor (v.set(s); !c; v.increment(i))Loop variable mutated directly; post-loop value preserved
MOVE x TO yy.set(x.get()) or direct assignmentType-appropriate
DISPLAY msgSystem.out.println(msg)
STOP RUNreturn;
Sequential file (FD — display records)BufferedReader + line.substring()Text-safe records only
Sequential file (FD — COMP-3/binary)BufferedInputStream + CobolComp3ReaderByte-stream; never BufferedReader

Code comparison — COBOL packed-decimal file vs. Java byte stream:

cobol
FD TRANS-FILE.
01 TRANS-RECORD.
   05 TR-AMOUNT  PIC 9(7)V99 COMP-3.
   05 TR-NAME    PIC X(20).
java
// Java target (binary record — Rule 9)
final int RECORD_BYTES = 5 + 20; // 4 bytes COMP-3 + 20 bytes char
try (BufferedInputStream bis = new BufferedInputStream(
        new FileInputStream("TRANS-FILE.dat"))) {
    byte[] buf = new byte[RECORD_BYTES];
    while (CobolComp3Reader.readRecord(bis, buf)) {
        BigDecimal amount = CobolComp3Reader.unpack(buf, 0, 4);
        String name = new String(buf, 4, 20, StandardCharsets.ISO_8859_1).trim();
        processTransaction(amount, name);
    }
}

2.2 Fortran

Source ConstructJava OutputNotes
GOTO (L1, L2, L3), expr (computed)switch (expr) with no defaultOut-of-range silently skipped — Fortran semantics
IF (expr) neg, zero, pos (arithmetic)if (e<0) … else if (e==0) … else …Three-way branch
Label loop: 10 CONTINUE / body / IF (c) GOTO 10do { body; } while (c)Do-while, not while — body runs before check
DO var = start, end [, step]Standard for loopPost-loop var value documented in Section C
IMPLICIT NONEAll variables explicitly declaredCompile-time directive, no runtime effect
PRINT *, exprSystem.out.println(expr)Formatting gap noted (Fortran adds leading spaces)
CONTINUE(discarded)No-op label target; not Java continue
INTEGER :: vint v32-bit signed, direct mapping
REAL :: vfloat v (flagged in Section C if financial)Precision risk noted
DOUBLE PRECISION :: vdouble v

Code comparison — Fortran computed GOTO vs. Java switch:

fortran
      GOTO (20, 30, 40), I
20    PRINT *, 'I = 1'
      GOTO 100
30    PRINT *, 'I = 2'
      GOTO 100
40    PRINT *, 'I = 3'
100   PRINT *, 'Done'
java
// i == 11 here (post-loop) — out of range → no case matches → silent skip
switch (i) {
    case 1: System.out.println("I = 1"); break;
    case 2: System.out.println("I = 2"); break;
    case 3: System.out.println("I = 3"); break;
    // no default: intentional — matches Fortran out-of-range silent skip
}
System.out.println("Done");

Important: The engine traces variable state line-by-line. In the example above, I equals 11 after the accumulation loop exits. The switch correctly receives 11 (not 1, 2, or 3), so no case fires and "Done" is printed directly — matching Fortran's exact runtime output.


2.3 Classic ASP / VBScript

Source ConstructJava / Spring Boot Output
ASP page (<% … %>)@Controller method (Spring MVC) or HttpServlet
Response.Write("text")out.println("text") on HttpServletResponse writer
Response.Redirect(url)return "redirect:" + url
Request.Form("field")@RequestParam("field") String field
Request.QueryString("key")@RequestParam("key") String key
Session("key") = valuesession.setAttribute("key", value)
Session("key")session.getAttribute("key")
Server.MapPath("/path")servletContext.getRealPath("/path")
Server.CreateObject("ADODB.Connection")javax.sql.DataSource (injected)
On Error Resume Nexttry { … } catch (Exception e) { log.warn(…); }
HTML literals in outputPreserved as-is in out.println() calls

Code comparison — ASP session handling vs. Spring Boot:

asp
<%
  Session("UserName") = Request.Form("username")
  Response.Write("<h1>Welcome, " & Session("UserName") & "</h1>")
%>
java
@Controller
public class WelcomeController {
    @PostMapping("/welcome")
    public void welcome(
            @RequestParam("username") String username,
            HttpSession session,
            HttpServletResponse response) throws IOException {
        session.setAttribute("UserName", username);
        PrintWriter out = response.getWriter();
        out.println("<h1>Welcome, " + session.getAttribute("UserName") + "</h1>");
    }
}

2.4 PHP 4/5

Source ConstructJava / Spring Boot Output
$_POST["field"]@RequestParam("field")
$_GET["key"]@RequestParam("key")
$_SESSION["key"]session.getAttribute("key")
header("Location: $url")return "redirect:" + url
mysql_connect(h,u,p)DataSource (JDBC) — parameterized only
mysql_query($sql)jdbcTemplate.query() or PreparedStatement
echo "text<br>" (web)out.println("text<br>")
echo "text" (CLI)System.out.println("text")
die($msg) / exit($msg)Unchecked exception or HTTP error response
CLI script ($argv, no $_POST)public static void main(String[] args)

SQL Injection Prevention: Every mysql_* API call is translated to a parameterized PreparedStatement or JdbcTemplate call. Raw string-concatenated SQL is never emitted.


3. Key Concepts & The Emulation Layer

Why an Emulation Layer?

Legacy languages enforce semantics that Java does not — fixed-width character fields, 1-based array indexing, packed-decimal arithmetic, and ODO-controlled dynamic table sizes. Mapping these naively to Java primitives introduces subtle bugs:

Legacy assumptionNaive Java mappingWhat goes wrong
PIC X(10) is always exactly 10 charsStringTrailing spaces stripped; comparison failures
PIC 9(7)V99 is exactdoubleRounding errors accumulate over financial calculations
Arrays are 1-indexedint[] (0-based)Off-by-one on every access
ODO table size tracks a variableArrayList with manual sizeSize drift when the controlling variable changes

The emulation layer solves each of these with a minimal set of wrapper classes.

CobolString

java
CobolString name = new CobolString(10); // PIC X(10)
name.set("ALICE");    // stored as "ALICE     " (5 chars + 5 spaces)
name.set("VERY LONG NAME"); // stored as "VERY LONG " (truncated at 10)
name.get();           // returns "ALICE     " — preserves trailing spaces
name.trimmed();       // returns "ALICE" — for display/comparison
  • Internally a char[] of the declared width.
  • set() fills from left, pads right with spaces, truncates if longer.
  • toString() returns the padded form (matching COBOL field output).

CobolInteger

java
CobolInteger count = new CobolInteger(3, 2); // PIC 99 VALUE 3
count.set(150);   // stored as 99 (max = 10^2 - 1 = 99)
count.increment(1); // now 100 → stored as 99 (capped)
  • Enforces the maximum value derived from the digit count.
  • toString() zero-pads to the declared width ("03" for PIC 99 with value 3).

CobolDecimal

java
CobolDecimal balance = new CobolDecimal("0", 2); // PIC 9(7)V99
balance.add(new BigDecimal("1000.505")); // stored as 1000.51 (HALF_EVEN)
balance.multiply(new BigDecimal("1.05")); // exact BigDecimal arithmetic
  • All arithmetic uses BigDecimal with MathContext.DECIMAL128.
  • Rounding mode HALF_EVEN (banker's rounding) matches COBOL's default.
  • Scale (decimal places) enforced on every set() call.
  • Never uses double or float internally.

CobolTable

java
CobolInteger wsCount = new CobolInteger(3, 2);
CobolTable<Entry> table = new CobolTable<>(1, 10, wsCount, Entry::new);

table.get(1).name.set("ALICE"); // 1-based — maps to data[0]
table.get(3).name.set("CHARLIE");

wsCount.set(5); // table.logicalSize() now returns 5 automatically
  • get(n) uses 1-based indexing; throws ArrayIndexOutOfBoundsException if out of bounds.
  • Pre-allocated to maxCapacity; accessing any slot within capacity returns a default entry rather than throwing.
  • The dependingOn reference keeps logical size synchronized with the COBOL ODO variable.
  • Bounds exception message includes: the 1-based index, the capacity range, and the current ODO variable value — making root-cause diagnosis fast.

CobolComp3Reader

java
// Reading a 5-byte COMP-3 field at offset 0 in a 25-byte record buffer
BigDecimal amount = CobolComp3Reader.unpack(recordBuf, 0, 5);
// 5 bytes → 9 decimal digits + sign nibble
// e.g. 0x01 0x23 0x45 0x67 0x8C → 12345678 (positive)
  • Each byte holds two BCD nibbles; the last nibble is the sign (0x0C/0x0F = positive, 0x0D = negative).
  • readRecord(bis, buf) handles partial I/O reads and clean EOF detection.
  • Before unpacking, validates offset + length <= buf.length; throws BufferOverflowException with field coordinates if violated.

4. Configuration & Customization Guide

4.1 Execution Context: Web vs. CLI

The engine performs a Context Assessment before generating any code. You can also force the target architecture explicitly.

Auto-detection (default)

The engine scans the source for web markers:

Marker foundDetected contextJava output
Response.Write, Request.Form, $_POSTWeb templateSpring Boot @Controller
<html>, <form>, <body>Web templateSpring Boot @Controller
session_start(), Session("key")Web templateSpring Boot @Controller
$argv, System.out, console-only outputCLI / Batchpublic static void main
No web markersCLI / Batchpublic static void main

Forcing a target architecture

Add a business context hint in the translator's context field:

code
Context: "Target a standalone Spring Boot REST API — @RestController with
          JSON responses, not HTML output"
code
Context: "This is a nightly batch job — generate a CLI application with
          main(String[] args) even if HTTP headers are present in the source"

4.2 Selecting the Target Framework

Context hintWhat the engine generates
(default for web)Spring Boot @Controller with Model / Thymeleaf view
"REST API" / "JSON response"@RestController with ResponseEntity<> returns
"Servlet" / "no Spring"Raw HttpServlet with doGet/doPost
"CLI" / "batch"public static void main(String[] args)
"Spring Boot microservice"Full @SpringBootApplication with auto-configuration

4.3 File I/O Mode

The engine auto-detects binary records from FD definitions. You can override:

code
Context: "All files are fixed-length text records (EBCDIC display) —
          use BufferedReader for all file I/O"
code
Context: "File TRANS-FILE is packed binary (COMP-3 throughout) —
          use BufferedInputStream and CobolComp3Reader"

4.4 BigDecimal Scale Override

By default, the scale is derived from the COBOL V clause (e.g., PIC 9(7)V99 → scale 2). To override:

code
Context: "All monetary fields require 4 decimal places for internal
          precision — use scale 4 on all CobolDecimal instances"

4.5 Freemium Tier Limits

TierFiles per migrationMax code sizeLanguage pairs
Anonymous150 KBJavaScript, Python 2, PHP 4 → TypeScript / Python
Free5/day100 KBAll 14 source languages
ProUnlimited5 MBAll pairs + autonomous multi-file
EnterpriseUnlimited25 MBAll pairs + GitHub PR integration

5. Troubleshooting & Edge Cases

5.1 Variable State After Loops

Symptom: The translated Java produces wrong output from a switch or if block that follows a loop.

Root cause: The legacy loop exits with the variable at its first-failing value, not its last-matching value. This is standard COBOL and Fortran behavior. A naive translator may incorrectly reset the variable or use its initial value.

What to check:

  • In COBOL PERFORM VARYING WS-I FROM 1 BY 1 UNTIL WS-I > 10, after the loop WS-I equals 11, not 10.
  • In Fortran DO I = 1, 10, after the loop I is implementation-defined but typically equals 11. The engine documents this in Section C of the migration notes.
  • If the translated switch after the loop never fires, trace the variable's post-loop value — it may be out of the case range, which is correct (Fortran computed GOTO out-of-range = silent skip).

5.2 Database / JDBC Configuration

Symptom: A translated PHP or ASP program that calls mysql_connect() / ADODB.Connection compiles but throws a NullPointerException or DataSourceException at runtime.

What to check:

  1. The engine generates a DataSource injection point — you must configure the actual connection in application.properties or application.yml:
yaml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/your_db
    username: your_user
    password: your_password
    driver-class-name: com.mysql.cj.jdbc.Driver
  1. Confirm the JDBC driver is on the classpath. Add to pom.xml:
xml
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>
  1. Every translated SQL statement uses PreparedStatement parameters — review Section C migration notes for the exact parameter binding order.

5.3 Binary File I/O Mismatches

Symptom: A COBOL batch program migrated with CobolComp3Reader reads records but produces garbled numeric values.

What to check:

  1. Record length — the declared RECORD CONTAINS n CHARACTERS must match RECORD_BYTES in the generated Java constant. Packed COMP-3 bytes are not the same as the number of decimal digits:
    • PIC 9(5)V99 COMP-3(7 digits + 1 sign nibble) / 2 = 4 bytes, not 7.
    • Formula: ceil((total_digits + 1) / 2) bytes.
  2. Sign nibble — some legacy platforms use 0x0C for positive and 0x0F for unsigned; others use only 0x0C. Check the source system's documentation and adjust CobolComp3Reader.unpack() if needed.
  3. EBCDIC vs. ASCII — character fields in a COMP-3 file may be EBCDIC-encoded. Use new String(buf, offset, length, Charset.forName("IBM037")) instead of StandardCharsets.ISO_8859_1.

5.4 Spring Boot Controller Not Mapping Requests

Symptom: An ASP or PHP page translated to a Spring Boot @Controller returns 404 or a blank response.

What to check:

  1. Confirm the @RequestMapping path in the generated controller matches the URL your application server expects.
  2. For Response.Write(html)out.println(html): ensure the method sets Content-Type:
    java
    response.setContentType("text/html; charset=UTF-8");
    PrintWriter out = response.getWriter();
  3. If the original ASP page used Server.Transfer (internal redirect), the engine maps it to Spring's forward: prefix — confirm the target URL exists in the application.

5.5 CobolTable Index Out of Bounds

Symptom: Runtime throws:

code
ArrayIndexOutOfBoundsException: COBOL 1-based index 11 out of table bounds [1, 10]
(ODO control variable current value: 11). Check loop termination condition or ODO variable mutation.

What to check:

  • The ODO variable (wsCount in this example) was incremented past maxCapacity (10) before table access.
  • In COBOL, accessing beyond the ODO logical size is undefined; beyond maxCapacity is always illegal.
  • Review whether the PERFORM VARYING loop should have its UNTIL condition adjusted, or whether maxCapacity in CobolTable needs to be raised to match the actual table definition.

5.6 Fortran CONTINUE Mistranslated

Symptom: A Fortran loop exits prematurely or a loop body is unexpectedly skipped.

Root cause: A translator that naively maps Fortran CONTINUE to Java continue breaks the control flow. Fortran CONTINUE is a label target no-op; Java continue jumps to the next loop iteration.

What to check:

  • Search the generated Java for continue; statements that appear without an if guard. If one corresponds to a Fortran CONTINUE label, it should be removed entirely.
  • The engine discards Fortran CONTINUE and does not emit Java continue.

5.7 COBOL 88-Level Conditions / REDEFINES / COPY

These constructs require manual review and are flagged in Section C of every translation:

ConstructWhat to do
88 NAME VALUE 'X'Translate to a Java boolean helper method or enum manually
REDEFINESThe overlapping memory layout cannot be auto-translated; implement as a Java union-style class manually
COPY memberInline the copybook member definition before submitting to the engine
EVALUATE WHENThe engine translates simple cases; complex multi-condition EVALUATE requires manual review

This guide reflects engine version as of commit cd504144 on branch feature/autonomous-legacy-code.

Migration Engine — User Guide | Jokalala | Jokalala