Skip to content

feat: Migrate COBOL codebase to Java Spring Boot application#72

Open
devin-ai-integration[bot] wants to merge 2 commits intomainfrom
devin/1774366536-spring-boot-migration
Open

feat: Migrate COBOL codebase to Java Spring Boot application#72
devin-ai-integration[bot] wants to merge 2 commits intomainfrom
devin/1774366536-spring-boot-migration

Conversation

@devin-ai-integration
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot commented Mar 24, 2026

Summary

Adds a new spring-boot-migration/ directory containing a complete Java 17 / Spring Boot 3.2.5 application that translates every COBOL example program into idiomatic Java. No existing files are modified.

6 phases implemented:

  • Phase 1 — Data Layer: Flyway migration (V1__create_accounts_table.sql with 11 seed rows), JPA Account entity, AccountRepository with custom LIKE search query, AccountService, REST AccountController (full CRUD), GlobalExceptionHandler
  • Phase 2 — Serialization: Jackson JSON + JAXB XML serialization of RecordDto/RecordXmlDto, exposed via /api/serialize/*
  • Phase 3 — File Processing: Fixed-width CustomerRecord parsing (135-char records), MergeSortService (merge/sort with Comparators), ReportService replicating COBOL Report Writer page layout
  • Phase 4 — Utilities: StringUtils (trim/unstring/isNumeric), DataRedefines (ByteBuffer REDEFINES simulation), SearchUtils (linear/binary), NumericUtils (NUMVAL, COMP mappings), CommandLineApp (Picocli CLI)
  • Phase 5 — Subprogram Architecture: MutableHolder<T> for BY REFERENCE semantics, SubProgramService with persistent working-storage, MainAppService demo
  • Phase 6 — Terminal UI Replacement: SystemInfoController (date/time/env endpoints), static index.html web frontend

100 tests across 12 test classes, all passing against H2 in PostgreSQL-compatibility mode.

Updates since last revision

  • Fixed DateTimeFormatter pattern: Changed "HHmmssnn""HHmmssSS" in SystemInfoController to correctly produce COBOL-style hundredths-of-second output instead of nanoseconds
  • Fixed stored XSS in index.html: Added escapeHtml() helper and applied it to all innerHTML interpolations (account fields, search query title, system info values, error messages)
  • Fixed @JsonInclude(NON_EMPTY) suppressing false: Added @JsonInclude(JsonInclude.Include.ALWAYS) on the enabled boolean field in RecordDto so it always appears in JSON output

Review & Testing Checklist for Human

  • Flyway migration SQL + JPA queries on real PostgreSQL: Tests run on H2 with MODE=PostgreSQL. The searchAccounts @Query uses LOWER('%' || ?1 || '%') concatenation and ESCAPE '' — verify this works on actual PostgreSQL. The SERIAL type and CURRENT_TIMESTAMP defaults in the migration should also be validated against a real DB.
  • CommandLineApp as @Component + Picocli: This Runnable component may execute on application startup via the picocli-spring-boot-starter. Verify the app starts cleanly as a web server without the CLI runner interfering.
  • Thread safety of SubProgramService: Working-storage simulation uses mutable instance fields on a Spring singleton. Concurrent requests will share/corrupt this state. Acceptable for a demo but worth noting if this will see real traffic.
  • DataRedefines charset assumptions: writeString/readString use the platform default charset. Behavior may differ across environments. COBOL REDEFINES operates on raw bytes with known encoding — the Java simulation may diverge.
  • README setup instructions say password: password but application.yml actually uses ${DB_PASSWORD:} (empty default). These are inconsistent.

Recommended test plan:

  1. Run mvn test locally to confirm all 100 tests pass
  2. Stand up a local PostgreSQL, create cobol_db_example database, set DB_PASSWORD, run mvn spring-boot:run, and hit the /api/accounts endpoints to verify Flyway + JPA work end-to-end
  3. Spot-check the COBOL-to-Java mapping table in the README against the original COBOL sources for any missed programs

Notes

  • No CI is configured on this repository, so mvn test must be run locally to validate
  • The @MockBean import uses the deprecated org.springframework.boot.test.mock.mockito.MockBean path (correct for Spring Boot 3.2.x, but will need updating if upgrading to 3.4+)
  • AccountController only handles enabled=N filter explicitly; other values fall through to getAllAccounts() returning all records
  • The serialization tests assert on string-contains rather than structural JSON/XML parsing — they may pass even if the output structure is subtly wrong

Link to Devin session: https://app.devin.ai/sessions/f0b401b93c76407aabd32527f99e65a0
Requested by: @jerryoliphant-cog


Open with Devin

Complete migration of all COBOL examples to a Spring Boot 3.2.5 REST API:

Phase 1: Foundation & Data Layer
- Flyway migration with accounts table and seed data
- JPA Entity, Repository, Service, Controller for CRUD operations
- GlobalExceptionHandler with @ControllerAdvice

Phase 2: Data Serialization
- Jackson JSON and JAXB XML serialization services
- RecordDto/RecordXmlDto replacing COBOL ws-record

Phase 3: File Processing & Reporting
- CustomerRecord with fixed-width parsing (135-char records)
- MergeSortService replacing COBOL MERGE/SORT statements
- ReportService replicating Report Writer layout

Phase 4: String Processing & Utilities
- StringUtils (trim, unstring, isNumeric)
- DataRedefines (ByteBuffer-based REDEFINES simulation)
- SearchUtils (linear/binary search)
- NumericUtils (NUMVAL, COMP type mappings)
- CommandLineApp with Picocli

Phase 5: Subprogram Architecture
- MutableHolder<T> for BY REFERENCE semantics
- SubProgramService with working-storage persistence
- MainAppService demonstrating calling conventions

Phase 6: Terminal UI Replacement
- SystemInfoController for date/time/env endpoints
- Web frontend replacing terminal menu system

100 tests passing across 12 test classes.

Co-Authored-By: Jerry Oliphant <jerry.oliphant@cognition.ai>
@devin-ai-integration
Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link
Copy Markdown
Author

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

View 5 additional findings in Devin Review.

Open in Devin Review

LocalTime now = LocalTime.now();
LocalDateTime dateTime = LocalDateTime.now();
Map<String, String> timeInfo = new LinkedHashMap<>();
timeInfo.put("time_hhmmssnn", now.format(DateTimeFormatter.ofPattern("HHmmssnn")));
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Invalid DateTimeFormatter pattern nn produces wrong time output

The pattern "HHmmssnn" at line 62 is incorrect. In Java's DateTimeFormatter, n is the pattern letter for nano-of-second (0–999999999), not hundredths of a second. With nn, the formatter outputs the full nano-of-second value with a minimum width of 2 digits. For example, at time 14:30:45.120000000, nn produces "120000000", making the full output "143045120000000" instead of the expected 8-character COBOL format "14304512". The correct pattern to replicate COBOL's hhmmssnn (hundredths of seconds) is "HHmmssSS", where S is fraction-of-second.

Suggested change
timeInfo.put("time_hhmmssnn", now.format(DateTimeFormatter.ofPattern("HHmmssnn")));
timeInfo.put("time_hhmmssnn", now.format(DateTimeFormatter.ofPattern("HHmmssSS")));
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e22c878 — changed pattern from "HHmmssnn" to "HHmmssSS" to correctly output hundredths of a second instead of nanoseconds.

Comment on lines +29 to +30
@JsonProperty("enabled")
private boolean flag;
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 @JsonInclude(NON_EMPTY) suppresses boolean enabled field when false

The class-level @JsonInclude(JsonInclude.Include.NON_EMPTY) annotation at spring-boot-migration/src/main/java/com/cobolmigration/dto/RecordDto.java:17 causes the flag field (serialized as "enabled") to be excluded from JSON output when its value is false. Jackson's BooleanSerializer.isEmpty() returns true for false values, so NON_EMPTY treats false as empty and suppresses it. This means the JSON output for a record with flag=false will be missing the "enabled" key entirely, which doesn't match the intended COBOL behavior where ws-record-flag always has a value ("true" or "false"). The test toJson_withFlagDisabled_shouldShowFalse at spring-boot-migration/src/test/java/com/cobolmigration/service/SerializationServiceTest.java:62-67 expects "false" in the output and would fail. The fix is to add a field-level @JsonInclude(JsonInclude.Include.ALWAYS) on the flag field to override the class-level setting.

Suggested change
@JsonProperty("enabled")
private boolean flag;
@JsonProperty("enabled")
@JsonInclude(JsonInclude.Include.ALWAYS)
private boolean flag;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e22c878 — added @JsonInclude(JsonInclude.Include.ALWAYS) on the flag field to ensure "enabled": false is always present in JSON output.

Comment on lines +80 to +83
accounts.forEach(a => {
html += `<tr><td>${a.id}</td><td>${a.firstName}</td><td>${a.lastName}</td>` +
`<td>${a.phone}</td><td>${a.address}</td><td>${a.isEnabled}</td></tr>`;
});
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Stored XSS vulnerability via unsanitized account data in innerHTML

The displayAccounts function at lines 80-82 directly interpolates account field values (a.firstName, a.lastName, a.phone, a.address) into HTML using template literals and assigns the result to innerHTML without any HTML escaping. Since account data is user-supplied via the POST /api/accounts endpoint, an attacker can create an account with a malicious firstName like <img src=x onerror=alert(document.cookie)> and it will execute as HTML/JavaScript for any user viewing the accounts list. The same issue affects the search title at line 73/78 where the raw query string q is interpolated into innerHTML.

Prompt for agents
In spring-boot-migration/src/main/resources/static/index.html, the displayAccounts function (lines 77-86) directly interpolates untrusted data into innerHTML using template literals. This causes stored XSS. Add an HTML-escaping helper function like:

function escapeHtml(str) {
    if (str == null) return '';
    return String(str).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}

Then use it in displayAccounts (lines 80-82) to wrap each interpolated value:
  html += `<tr><td>${escapeHtml(a.id)}</td><td>${escapeHtml(a.firstName)}</td><td>${escapeHtml(a.lastName)}</td><td>${escapeHtml(a.phone)}</td><td>${escapeHtml(a.address)}</td><td>${escapeHtml(a.isEnabled)}</td></tr>`;

Also escape the title parameter in line 78:
  let html = `<h2>${escapeHtml(title)}</h2><table>`;

And the search query in searchAccounts at line 73:
  displayAccounts(accounts, `SEARCH RESULTS FOR "${escapeHtml(q)}":`);

And the error message in showError at line 105:
  document.getElementById('results').innerHTML = `<p class="error">Error: ${escapeHtml(e.message)}</p>`;

And the system info values in showSystemInfo at line 97:
  html += `<tr><td>${escapeHtml(k)}</td><td>${escapeHtml(v)}</td></tr>`;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e22c878 — added escapeHtml() helper and applied it to all innerHTML interpolations: account fields, search query title, system info values, and error messages.

…JsonInclude boolean

- Fix DateTimeFormatter pattern: 'nn' (nano) -> 'SS' (fraction-of-second) for
  COBOL hhmmssnn hundredths-of-second format
- Fix stored XSS: add escapeHtml() helper and sanitize all innerHTML interpolations
- Fix @JsonInclude(NON_EMPTY) suppressing boolean false: add field-level ALWAYS override

Co-Authored-By: Jerry Oliphant <jerry.oliphant@cognition.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants