feat: Migrate COBOL codebase to Java Spring Boot application#75
feat: Migrate COBOL codebase to Java Spring Boot application#75devin-ai-integration[bot] wants to merge 2 commits intomainfrom
Conversation
Complete 4-phase migration of COBOL-Examples to Spring Boot 3.2.5: Phase 0 - Foundation: - Spring Boot 3.2.5 project with Gradle 8.7 - PostgreSQL/JPA/Flyway configuration via env vars - Flyway migration mirroring create_test_db.sql Phase 1 - Data Layer: - Account JPA entity and repository (replaces sql_example.cbl) - JSON serialization with Jackson (replaces json_generate.cbl) - XML serialization with JAXB (replaces xml_generate.cbl) - Custom exception handling with ControllerAdvice Phase 2 - Business Logic: - StringUtils: trim, unstring, isNumeric, numval - SearchUtils: binary/sequential search (replaces SEARCH ALL) - FileSortService: merge/sort (replaces MERGE/SORT) - ReportService: report generation (replaces Report Writer) - SubProgramService: subprogram pattern (replaces CALL/CANCEL) - Model classes: Customer, CustomerRecord, SearchItem Phase 3 - Interface Layer: - REST API: AccountController, SerializationController, UtilityController - CLI Runner with interactive menu (mirrors COBOL terminal UI) Phase 4 - Testing: - 61 tests across 8 test classes (all passing) - AccountRepositoryTest, AccountControllerTest (WebMvcTest) - SerializationServiceTest, StringUtilsTest, SearchUtilsTest - FileSortServiceTest, ReportServiceTest - Golden test fixtures for output verification - docker-compose.yml for local PostgreSQL Co-Authored-By: Jerry Oliphant <jerry.oliphant@cognition.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| * Formats the record as a fixed-width string similar to COBOL DISPLAY output. | ||
| */ | ||
| public String toFixedWidthString() { | ||
| return String.format("%-5d%-50s%-50s%-5d%-25s", |
There was a problem hiding this comment.
🔴 toFixedWidthString uses space-padded format instead of zero-padded for integer fields
The String.format("%-5d", ...) format specifier in toFixedWidthString() produces left-justified, space-padded integers (e.g., "1 " for customerId=1 and "5423 " for contractId=5423). However, COBOL pic 9(5) fields are zero-padded, as confirmed by the golden file at spring-boot-migration/src/test/resources/golden/merge_sort_merged.txt which shows 00001 and 05423. The correct format specifier is %05d. This means any files written by this method are incompatible with the expected COBOL fixed-width record format. The round-trip test at FileSortServiceTest.java:88-103 doesn't catch this because parseFixedWidthRecord trims before parsing, which handles both space-padded and zero-padded formats.
| return String.format("%-5d%-50s%-50s%-5d%-25s", | |
| return String.format("%05d%-50s%-50s%05d%-25s", |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed — changed to %05d for zero-padded output matching COBOL pic 9(5) format.
| public class SubProgramService { | ||
|
|
||
| // Working-storage equivalents: persist between calls | ||
| private String wsTestItem1 = ""; | ||
| private String wsTestItem2 = ""; |
There was a problem hiding this comment.
🔴 SubProgramService singleton has unsynchronized mutable state causing race conditions
The @Service-annotated SubProgramService is a singleton bean by default in Spring. Its mutable instance fields wsTestItem1 and wsTestItem2 (lines 30-31) are read and written in processByContent() and processByReference() without any synchronization. In the web application mode, concurrent HTTP requests to /api/utility/subprogram from the embedded Tomcat thread pool will race on these fields, causing one request to observe another request's state. For example, thread A could set wsTestItem1 = "foo", then thread B sets wsTestItem1 = "bar", and thread A's result.wsItem1AtEnd would read "bar" instead of "foo".
Prompt for agents
The SubProgramService is a Spring singleton bean with mutable instance fields (wsTestItem1, wsTestItem2) that are read/written by concurrent HTTP request threads without synchronization. This causes race conditions between concurrent callers of processByContent(), processByReference(), and reset().
Possible fixes:
1. Add synchronized keyword to all three methods (processByContent, processByReference, reset).
2. Use @Scope("prototype") or @RequestScope to give each request its own instance.
3. Store working-storage state in a ConcurrentHashMap keyed by session/request ID if per-user state is needed.
Option 1 is the simplest and closest to the original COBOL single-threaded semantics, but serializes all subprogram requests. Option 2 or 3 would be better for concurrent access but changes the COBOL working-storage persistence semantics (state wouldn't persist between separate HTTP requests).
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed — added synchronized to all three methods (processByContent, processByReference, reset). This matches the original single-threaded COBOL semantics where subprogram calls are serialized.
| datasource: | ||
| url: ${DB_URL:jdbc:postgresql://localhost:5432/cobol_db_example} | ||
| username: ${DB_USERNAME:postgres} | ||
| password: ${DB_PASSWORD:} |
There was a problem hiding this comment.
🟡 Default DB password mismatch between application.yml and docker-compose.yml breaks Quick Start
The application.yml defaults DB_PASSWORD to an empty string (password: ${DB_PASSWORD:}), while docker-compose.yml defaults POSTGRES_PASSWORD to changeme (${DB_PASSWORD:-changeme}). When a user follows the README Quick Start without setting the DB_PASSWORD environment variable, the PostgreSQL container will be configured with password changeme, but the Spring Boot application will attempt to connect with an empty password, resulting in a connection failure.
| password: ${DB_PASSWORD:} | |
| password: ${DB_PASSWORD:changeme} |
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Fixed — aligned application.yml default to changeme to match docker-compose.yml.
- CustomerRecord: use zero-padded format (%05d) for integer fields to match COBOL pic 9(5) behavior - SubProgramService: add synchronized to all methods to prevent race conditions on shared mutable state - application.yml: align default DB_PASSWORD with docker-compose.yml default (changeme) Co-Authored-By: Jerry Oliphant <jerry.oliphant@cognition.ai>
Summary
Adds a complete Java Spring Boot 3.2.5 application under
spring-boot-migration/that reimplements all COBOL example programs from this repository. The migration covers database access (SQL/ODBC → JPA), serialization (JSON GENERATE/XML GENERATE → Jackson/JAXB), string processing, data structures, file operations, report generation, and the terminal menu UI (→ REST API + CLI runner).46 files added, organized into standard Spring Boot layers:
model/,repository/,service/,controller/,util/,cli/,exception/. 61 unit/integration tests pass against an in-memory H2 database. No existing files were modified.Key COBOL → Java mappings:
sql/sql_example.cbl→Accountentity +AccountRepository(JPA) +AccountService+ REST endpointsjson_generate/,xml_generate/→SerializationServicewith Jackson/JAXBtrim/,unstring/,is_numeric/,numval_test/→StringUtilsstatic methodssearch/→SearchUtils(binary + sequential search)merge_sort/→FileSortService,report_writer/→ReportServicesub_program/→SubProgramServicewith by-content/by-reference semanticsCliRunner(CommandLineRunner) +AccountControllerREST APIUpdates since last revision
Fixed 3 bugs identified by Devin Review:
CustomerRecord.toFixedWidthString(): Changed%-5d→%05dfor integer fields to match COBOLpic 9(5)zero-padded formatSubProgramService: AddedsynchronizedtoprocessByContent(),processByReference(), andreset()to prevent race conditions on shared mutable singleton stateapplication.yml: Aligned defaultDB_PASSWORDtochangemeto matchdocker-compose.yml, so Quick Start works without extra env var setupReview & Testing Checklist for Human
StringUtils.unstring()edge cases (consecutive delimiters, empty fields),FileSortService.parseFixedWidthRecord()field widths,ReportServicepagination constants (DETAILS_PER_PAGE=37, PAGE_LIMIT=66 — do these match the COBOL RD definition?).src/test/resources/golden/but no test actually loads or asserts against them. These are placeholder files, not real verification. Consider whether tests should actually compare Java output against these fixtures.UtilityControllerorSerializationController: OnlyAccountControllerhas@WebMvcTesttests. The utility and serialization endpoints are untested at the controller layer.V1__create_accounts_table.sqlexactly matchessql/create_test_db.sqlcolumn types, defaults, and sample data. Tests bypass Flyway (H2 withddl-auto: create-drop), so migration SQL is never exercised.Recommended test plan:
./gradlew testto confirm all 61 tests passdocker-compose up -d, run./gradlew bootRun, and hit a few REST endpoints (GET /api/accounts,POST /api/serialize/json) to verify end-to-endNotes
DB_URL,DB_USERNAME,DB_PASSWORD) with defaults that matchdocker-compose.yml./gradlew buildonlydocker-compose.ymlversionkey is deprecated in newer Compose versions but still functionalLink to Devin session: https://app.devin.ai/sessions/09da5e8db11f41e9a303bf7109570d2f
Requested by: @jerryoliphant-cog