Case Study

Legacy Flat-File to JSON Bridge

Modern zero-dependency Java bridge that ingests fixed-width mainframe-style records and serializes nested JSON — records, text blocks, manual byte-range parsing.

  • Java
  • NIO
  • records
  • text blocks
  • java
  • legacy
  • mainframe
  • json
  • enterprise
  • zero-dependency

Overview

This project simulates modernizing legacy mainframe data. It ingests an ugly, fixed-width sequential file — a format common in z/OS, banking, and DB2 batch dumps — and parses, sanitizes, and serializes it into structured, nested JSON.

To keep with the theme of Project 1 and Project 2, this is written in modern, zero-dependency Java (utilizing features like records and text blocks). It handles complex parsing challenges natively, including manual byte-range slicing, strict data type conversion, whitespace trimming, and custom JSON serialization.

What it implements

  • Fixed-width field slicing — strict character-coordinate parsing for legacy batch layouts
  • Implicit decimal conversion — balance stored as cents (0000452050$4,520.50)
  • Code-table mappingCHK/SVG type codes and A/I status flags
  • Malformed line tolerance — skips corrupt rows with terminal warnings
  • Native JSON output — no Jackson or Gson; text blocks format each record

Schema layout

The bridge parses fixed positions based on this layout:

FieldPositionsLengthNotes
ID0–910Account identifier
Name10–2920Customer name, padded
Balance30–3910Implicit cents (no decimal point)
Type code40–423CHK = Checking, SVG = Savings
Status431A = Active, I = Inactive
Last updated44–5310YYYY-MM-DD

Each line must be exactly 54 characters.


Project setup

1. Create project files

mkdir legacy-bridge && cd legacy-bridge
touch legacy_accounts.txt LegacyBridge.java

2. Sample fixed-width data

Open legacy_accounts.txt and paste the following. Fixed-width files rely on exact character spacing — columns must line up exactly:

1001429584COLE, DAVID        0000452050CHKA2026-04-12
2008511409SMITH, AMANDA      0012500075SVGA2025-11-30
3004419200RODRIGUEZ, CARLOS  0000000000CHKI2026-01-15
4009725513CHENG, WEI         0000089000SVGA2026-05-01

The code (LegacyBridge.java)

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class LegacyBridge {

    // --- Modern Java Data Structures ---
    public record Account(
        String id,
        String fullName,
        double balance,
        String accountType,
        boolean isActive,
        String lastUpdated
    ) {
        // Natively formats the record into a clean JSON object string
        public String toJsonString() {
            return """
                {
                  "id": "%s",
                  "customerName": "%s",
                  "balance": %.2f,
                  "type": "%s",
                  "active": %b,
                  "lastUpdated": "%s"
                }""".formatted(id, fullName, balance, accountType, isActive, lastUpdated);
        }
    }

    // --- Parsing Core Logic ---
    public static Account parseLine(String line) {
        // Protect against corrupt or truncated batch lines
        if (line == null || line.length() < 54) {
            throw new IllegalArgumentException("Invalid layout segment: Line length must be exactly 54 characters.");
        }

        // Slice fields strictly by character coordinates
        String rawId = line.substring(0, 10).trim();
        String rawName = line.substring(10, 30).trim();
        String rawBalance = line.substring(30, 40).trim();
        String rawType = line.substring(40, 43).trim();
        String rawStatus = line.substring(43, 44).trim();
        String rawDate = line.substring(44, 54).trim();

        // Sanitize and transform raw legacy strings to modern data types
        double balanceInDollars = Double.parseDouble(rawBalance) / 100.0;

        String accountType = switch (rawType) {
            case "CHK" -> "Checking";
            case "SVG" -> "Savings";
            default -> "Unknown (" + rawType + ")";
        };

        boolean isActive = rawStatus.equalsIgnoreCase("A");

        return new Account(rawId, rawName, balanceInDollars, accountType, isActive, rawDate);
    }

    // --- Orchestration Routine ---
    public static void main(String[] args) {
        String inputFileName = "legacy_accounts.txt";
        Path inputPath = Paths.get(inputFileName);

        System.out.println("\033[96m⚙️ Initializing Legacy Flat-File Transformation Bridge...\033[0m");

        if (!Files.exists(inputPath)) {
            System.err.println("\033[91mError: Target sequential file '" + inputFileName + "' missing.\033[0m");
            System.exit(1);
        }

        try {
            // Stream the file lines natively using NIO
            List<String> lines = Files.readAllLines(inputPath);
            List<Account> parsedAccounts = new ArrayList<>();

            for (String line : lines) {
                if (line.isBlank()) continue;
                try {
                    parsedAccounts.add(parseLine(line));
                } catch (Exception e) {
                    System.err.println("\033[93m[Skipping Malformed Line] " + e.getMessage() + "\033[0m");
                }
            }

            // Serialize the array collection manually to enforce formatting without third-party tools
            String jsonOutput = parsedAccounts.stream()
                .map(Account::toJsonString)
                .collect(Collectors.joining(",\n", "[\n", "\n]"));

            // Write transformed dataset back to disk
            Path outputPath = Paths.get("accounts_transformed.json");
            Files.writeString(outputPath, jsonOutput);

            // Display success telemetry directly to terminal output
            System.out.println("\033[92mSuccessfully modernized data structures!\033[0m");
            System.out.println("📄 Output generated: " + outputPath.toAbsolutePath());
            System.out.println("\n--- Modernized JSON Payload Output Sample ---");
            System.out.println(jsonOutput);

        } catch (IOException e) {
            System.err.println("\033[91mCritical System Failure executing data transformation pipeline:\033[0m " + e.getMessage());
        }
    }
}

Execution

Single-file source execution — no Maven, Gradle, or dependency tree required:

java LegacyBridge.java

The bridge parses legacy_accounts.txt and writes accounts_transformed.json to the working directory.

Example output shape:

[
  {
    "id": "1001429584",
    "customerName": "COLE, DAVID",
    "balance": 4520.50,
    "type": "Checking",
    "active": true,
    "lastUpdated": "2026-04-12"
  }
]

Why this shines on a portfolio

  1. Solves an enterprise problem — proves you can ingest rigid legacy layouts: implicit decimal shifts, alpha-numeric state flags, and padded name fields that underpin older corporate stacks
  2. Advanced Java structures — text blocks and records over boilerplate POJOs and string concatenation
  3. Pairs with real modernization work — same parsing discipline as The Technomancer’s Path COBOL training platform, different layer of the pipeline