Recommendation and decision
Recommendation: bounded migration with H2-to-PostgreSQL database replacement and Spring Boot 3 / Java 21 upgrade as the minimum viable modernisation path, with a separately authorized Java invoice-preview pilot before committing to full migration scope.
The BASE application (com.ingramtrials.base, Spring Boot 2.7.18, Java 8, H2 file-mode) is a functioning single-JAR financial desk with 23 confirmed findings across security, data integrity, architecture and observability dimensions. Three security findings (F-001 SQL injection, F-002 credential exposure, F-003 unauthenticated batch triggers) and four high-severity structural findings (F-005 batch atomicity, F-016 H2-specific SQL, F-017 EOL dependencies, F-021 no backup/DR) make the codebase unsuitable for production deployment without remediation regardless of the migration path chosen.
The retain/contain option avoids migration disruption but does not resolve the EOL Spring Boot 2.7.x exposure, the SQL injection defects, or the absence of a backup regime. Bounded migration (H2 → PostgreSQL + Spring Boot 3 upgrade, with the existing Java architecture retained) addresses the highest-severity findings with lower delivery risk than a full rewrite. [a sentence held back]
Decision owner: Client technical and security owner (pending). Decision required: Confirm the migration target profile, authorize the Java invoice-preview pilot (separately gated), and provide a backup/DR policy decision before delivery scoping can proceed. Blockers: F-001 (SQL injection must be fixed regardless of path), F-005 (batch atomicity), F-017 (EOL dependency upgrade), F-021 (backup/DR policy), F-009 (InvoiceRunJobV2 owner disposition — owner and activation path unknown; see EV-004, EV-014, EV-030). Evidence that could change the recommendation: A confirmed production APM trace showing high concurrent load would increase the urgency of the synchronized-method architecture finding (F-012) and potentially justify an earlier microservice extraction. A confirmed CVE scan result matching an exploitable path in Spring Boot 2.7.18 would escalate F-017 to critical and compress the upgrade timeline.
Pilot boundary: The separately authorized Java invoice-preview pilot covers the Desk.calculate() path (preview only, no save or batch operations), with target Java 21 and Spring Boot 3 compatibility. Scope exclusions: database migration, job paths, authentication, browser SPA, and deployment to any production environment. [a sentence held back] Production deployment, security clearance and enterprise performance remain unverified.
[a sentence held back] Security findings F-001, F-002 and F-003 must be remediated and verified before any production exposure. Assessment status: pending independent review.
Current state and inspected evidence
| Component / entry point | Source path or run | Evidence ID / class | Observed fact | Uncertainty / owner action |
|---|---|---|---|---|
| H2 datasource | application.properties:3 | EV-001 / source_config | File-mode H2 at ./runtime/data/base; no production RDBMS configured | No backup/DR config; data volume unknown (EV-028) |
| Demo date / schedules | application.properties:7-10 | EV-002 / source_config | demo.today hardcoded 2026-10-13; schedules disabled by default | Whether schedules are enabled in any deployed environment is unknown (EV-031) |
| Server binding | application.properties:1-2 | EV-003 / source_config | Loopback 127.0.0.1:18080; no external binding in source | No reverse-proxy or network topology documentation |
| Job registry | jobs.properties:2-3 | EV-004 / source_config | 'invoices'→InvoiceRunJob; 'credit-notes'→CreditNoteJob; InvoiceRunJobV2 not registered | InvoiceRunJobV2 owner/disposition unknown (EV-030) |
| Desk @PostConstruct | Desk.java:36-59 | EV-005, EV-006 / source_config | 8 DDL statements + seed inserts at startup; jobs.properties loaded; missing resource throws ISE | No migration framework; no column evolution path |
| customers() search | Desk.java:93 | EV-007 / source_config | SQL injection via string-concatenated LIKE on q param | Not tested; no parameterized query present |
| invoices() search | Desk.java:129-132 | EV-008 / source_config | Same SQL injection pattern as customers() | Not tested |
| preview vs save discount | Desk.java:107-119 | EV-009 / source_config | previewLineNet uses >=100; bookedLineNet uses >100 — off-by-one at qty=100 | Business owner decision on intended boundary pending |
| Job dispatch | Desk.java:220-225 | EV-010 / source_config | Class.forName().newInstance() (deprecated since Java 9) | No compile-time binding; runtime failure on bad class name |
| save() / requestCredit() locking | Desk.java:177, :194 | EV-011, EV-012 / source_config | synchronized on Desk instance; COALESCE MAX+1 ID generation | Single-JVM safe; breaks under horizontal scaling |
| invoiceBatch() / creditBatch() | Desk.java:226-255 | EV-026, EV-027 / source_config | No outer transaction; partial failure leaves mixed state | No test confirms rollback behavior |
| /api/health | DeskController.java:23,30-31 | EV-018 / source_config | Echoes full spring.datasource.url in response body | No authentication guard on health endpoint in inspected source |
| /api/jobs/* endpoints | DeskController.java; desk.js:82-83 | EV-021 / source_config | Batch triggers invocable from browser with no auth check in inspected source | Spring Security configuration not found in inspected scope |
| monthly() / annual() reports | Desk.java:259, :285-286 | EV-022, EV-023 / source_config | FORMATDATETIME — H2-specific SQL function | Fails against PostgreSQL without rewrite |
| pom.xml | BASE/app/pom.xml | EV-024 / source_config | Spring Boot 2.7.18, Java 8, H2 2.2.224, spring-boot-starter-test present | CVE scan not performed; Spring Boot 2.7.x is EOL |
| InvoiceRunJobV2 | InvoiceRunJobV2.java:3-7 | EV-014, EV-030 / source_config, unknown | Stub returning 0; comment 'Unfinished replacement'; not in job registry | Owner and activation path unknown |
| LegacyInvoicePrinter | LegacyInvoicePrinter.java:1-6 | EV-004 (scope context) / source_config | No reference in any inspected source, test or configuration file | Full classpath and reflection scan not performed; dead-code candidate |
| GST constant | Desk.java:29, :248 | EV-032, EV-027 / source_config | GST_RATE=0.09 declared at class level; '0.09' also appears inline in creditBatch | Two-location duplication creates rate-change risk |
| Test suite | DeskMoneyTest.java | EV-024 / source_config | One test: money rounding to 2 decimal places | No integration, route, batch, job or discount-boundary tests |
| BaseApplication | BaseApplication.java:1 | EV-035 / inference | EnableScheduling + WebMvcConfigurer active; standard Spring Boot JAR | pom.xml packaging type not confirmed from source text |
Legacy code health and poor-practice findings
| Finding ID | Category | Observed condition and business/operational impact | Ordinal severity (1=critical … 5=informational) and rationale | Evidence IDs / class | Treatment / decision |
|---|---|---|---|---|---|
| F-001 | Security | SQL injection in customers() and invoices() via unparameterized LIKE — attacker can read/modify/delete all application data | 1 — critical; direct exploitation possible at API layer | EV-007, EV-008 / source_config | Fix before any deployment; parameterize queries |
| F-002 | Security | /api/health echoes full JDBC URL to any caller — leaks storage location and engine type | 2 — high; enables targeted DB file access if binding is changed | EV-018 / source_config | Remove or redact before deployment |
| F-003 | Security | Batch job trigger endpoints (POST /api/jobs/*) unauthenticated in inspected source | 2 — high; any user can trigger financial batch runs | EV-021 / source_config | Add Spring Security role guard; confirm coverage |
| F-004 | Error/data handling | Preview/save discount off-by-one at qty=100 — customer sees lower price on screen than booked | 2 — high; billing correctness defect with direct revenue impact | EV-009, EV-019 / source_config | Align boundary; business owner decision required |
| F-005 | Error/data handling | Batch jobs lack outer transaction — partial failure leaves mixed invoice/credit state | 2 — high; silent partial state is hard to detect and repair | EV-026, EV-027 / source_config | Add @Transactional scope to batch methods |
| F-016 | Obsolete dependency | H2 FORMATDATETIME in reporting queries — fails immediately against PostgreSQL | 2 — high; blocks any H2→PostgreSQL migration | EV-001, EV-022, EV-023 / source_config | Replace with portable date function before DB migration |
| F-017 | Obsolete dependency | Spring Boot 2.7.18 (EOL Nov 2023) + Java 8 source level — no upstream CVE patches | 2 — high; EOL dependency, CVE scan not performed | EV-024, EV-035 / source_config, inference | Upgrade to Spring Boot 3 / Java 21; run CVE scan |
| F-018 | Testability | One unit test (money rounding); no integration, route, batch or discount tests | 2 — high; no regression net for financial logic or migration | EV-024 / source_config | Build test suite before migration begins |
| F-021 | Operations | H2 file-mode database with no documented backup or DR policy — financial data loss risk unquantified | 2 — high; unmitigated data loss risk | EV-001, EV-028 / source_config, unknown | Owner decision on backup/DR policy required |
| F-006 | Error/data handling | [held back] | 3 — medium; silent batch failures undetectable from logs | EV-015, EV-016 / source_config | Add structured logging and exception handling in wrappers |
| F-008 | Architecture | Credit requests auto-approved with no workflow — no manager review or dollar-limit check | 3 — medium; bypasses financial controls | EV-034 / source_config | Business owner decision on approval workflow required |
| F-009 | Dead-code candidate | InvoiceRunJobV2 is an unregistered stub ('Unfinished replacement') — owner unknown | 3 — medium; ambiguity about intended implementation increases migration risk | EV-004, EV-014, EV-030 / source_config, unknown | Owner disposition required before migration |
| F-012 | Architecture | Coarse synchronized methods serialize all writes — scalability blocker for horizontal expansion | 3 — medium; safe now; breaks under multi-node deployment | EV-011, EV-012 / source_config | Replace COALESCE ID generation with DB sequences; remove JVM lock |
| F-013 | Architecture | COALESCE MAX+1 ID generation without DB sequence — duplicate key risk under any multi-node scenario | 3 — medium; migration blocker for horizontal or microservice extraction | EV-012, EV-013 / source_config | Introduce DB sequences before any scaling or DB migration |
| F-014 | Architecture | Inline DDL at @PostConstruct, no migration framework — no column evolution or rollback path | 3 — medium; architectural blocker for CI/CD-driven schema management | EV-005, EV-006 / source_config | Introduce Flyway or Liquibase |
| F-015 | Architecture | Reflective job dispatch via deprecated Class.newInstance() — no compile-time binding | 3 — medium; runtime crash risk on config error; deprecated API | EV-004, EV-010, EV-025 / source_config | Replace with Spring bean registry or compile-time dispatch |
| F-019 | Documentation | No API spec, no business rule documentation, no operational runbook | 3 — medium; migration engineers must reverse-engineer all behavior from source | EV-024, EV-029 / source_config, unknown | Owner action: document rules, create runbook before handoff |
| F-020 | Operations | Hardcoded demo date, disabled schedules, loopback binding — not production-ready defaults | 3 — medium; wrong results and inaccessibility in production deployment | EV-002, EV-003, EV-017, EV-029, EV-031 / source_config, unknown | Owner confirmation of production config path required |
| F-022 | Coupling | Job interface passes full Desk component — unrestricted access creates coupling risk | 3 — medium; latent risk for microservice extraction and isolation testing | EV-025 / source_config | Introduce narrower JobContext interface |
| F-007 | Error/data handling | 50-line invoice limit enforced server-side only; no UI feedback | 4 — low; poor UX; no correctness impact | EV-033 / source_config | Add client-side validation before client-facing deployment |
| F-010 | Dead-code candidate | LegacyInvoicePrinter not referenced in any inspected path | 4 — low; not confirmed dead — full reflection scan not performed | EV-004 (scope) / source_config | Perform full call-site scan; remove if confirmed unused |
| F-011 | Duplication | GST rate 0.09 duplicated as class constant and inline literal in creditBatch() | 4 — low; split-rate bug on tax rate change | EV-027, EV-032 / source_config | Replace inline literal with class constant |
| F-023 | Coupling | Browser SPA hardcodes /api base — same-origin deployment constraint undocumented | 4 — low; deployment blocker for CDN/API-gateway topology | EV-020 / source_config | Document constraint; add configurable base URL if needed |
Not evidenced in supplied scope: No scan was performed for additional code smells beyond the inspected 13 Java source files. Frontend JavaScript is partially inspected (desk.js). No dynamic analysis, DAST, SAST tool run, dependency vulnerability scan or penetration test was performed. "Scan not performed" applies to all scanner-based security evidence categories.
Architecture, dependencies and operations
| Dependency / call path / data flow | From → to | Evidence IDs / class | Operational consequence | Unknown and validation owner |
|---|---|---|---|---|
| Browser SPA → Spring MVC API | desk.js fetch('/api/*') → DeskController | EV-020, EV-021 / source_config | Same-origin deployment required; batch trigger endpoints exposed to browser users | No network trace or access log; actual traffic patterns unknown (EV-029) |
| DeskController → Desk | Direct method calls (calculate, save, runJob, requestCredit) | EV-018, EV-019 / source_config | [held back] | No runtime call trace; relationship inferred from source |
| Desk → H2 file database | JdbcTemplate SQL (DDL, DML, queries) | EV-001, EV-006, EV-007, EV-008, EV-022, EV-023 / source_config | SQL injection exposed; H2-specific functions block DB migration; no migration framework | No query execution plan or runtime SQL log |
| Spring Scheduling → ScheduledJobs → Desk.runJob() | @Scheduled cron → ScheduledJobs.invoiceRun/creditRun → Desk.runJob() | EV-002, EV-017, EV-031 / source_config | Cron triggers registered but execution suppressed by enabled flag; actual schedule activation in any environment unknown | Whether schedules run in production is unknown (EV-031) |
| Desk.runJob() → Job implementations (reflection) | Class.forName().newInstance() → InvoiceRunJob / CreditNoteJob | EV-004, EV-005, EV-010, EV-015, EV-016 / source_config | Runtime dispatch; misconfiguration causes startup or runtime crash | InvoiceRunJobV2 status unknown (EV-030); reflection scan not performed |
| invoiceBatch() / creditBatch() → H2 (batch DML) | Desk batch methods → JdbcTemplate multi-row DML | EV-026, EV-027, EV-012, EV-013 / source_config | No outer transaction; partial failure undetectable; COALESCE ID conflicts under scaling | No runtime execution log; batch behavior in production unknown |
| DeskController /api/health → spring.datasource.url | Injected property → JSON response | EV-018 / source_config | Internal DB path exposed to any /api/health caller | No access log showing actual caller scope |
| Desk @PostConstruct → classpath resource jobs.properties | Startup → Properties.load() | EV-004, EV-005 / source_config | Missing resource prevents application boot with no graceful fallback | Classpath confirmed in source; no test for missing-resource scenario |
Security assessment
| Finding ID | Defect/advisory/scan type | Component and affected-version state | Execution-path/reachability evidence | Status and remediation proof |
|---|---|---|---|---|
| F-001 | Code defect — SQL injection (CWE-89) | Desk.java customers(), invoices() — Spring Boot 2.7.18 / H2 2.2.224 | Reachable via GET /api/customers?q= and invoices search; no authentication guard in inspected source | under_investigation — confirmed defect from source; no runtime test or pentesting performed |
| F-002 | Configuration defect — credential exposure (CWE-200) | DeskController.java /api/health — Spring Boot 2.7.18 | Reachable via GET /api/health; no authentication guard in inspected source | under_investigation — confirmed from source; endpoint caller scope unknown |
| F-003 | Configuration defect — missing authorization (CWE-862) | DeskController.java /api/jobs/* — Spring Boot 2.7.18 | Reachable via POST /api/jobs/invoices and /api/jobs/credit-notes from browser button | under_investigation — no Spring Security config found in inspected scope; out-of-scope security filter cannot be confirmed or denied |
| F-008 | Process defect — missing access control on credit approval | requestCredit() — Desk.java:199-201 | Reachable via HTTP POST from any authenticated user | under_investigation — auto-approve behavior confirmed from source; intended design not confirmed |
| F-017 | Dependency advisory — Spring Boot 2.7.18 EOL (CVE scan not performed) | spring-boot-starter-parent 2.7.18; H2 2.2.224 | Spring Boot 2.7.x reached EOL November 2023; CVE scan not performed against pom.xml | under_investigation — EOL fact established; specific CVE affectedness and execution path not investigated |
Scan scope: No SAST, DAST, dependency vulnerability scanner (OWASP Dependency Check, Snyk, Trivy) or penetration test was performed. All security findings are derived from static source and configuration inspection. The absence of scanner results is not a clean score; it means "scan not performed."
Options and target tradeoffs
Three options are assessed for the overall estate. Target-specific detail is in target-annexes.md.
Option 1 — Retain and contain (Java 8 / Spring Boot 2.7.18 / H2): Remediate the security defects (F-001, F-002, F-003) and add a backup regime (F-021) without changing the platform. Lowest disruption and fastest delivery for the critical remediations. Does not resolve the EOL dependency risk (F-017), does not enable horizontal scaling (F-012), and defers the H2-to-production-DB gap (F-021 medium-term). Suitable as a short-term stabilisation step while the migration target is decided. Reversal evidence: CVE advisory on Spring Boot 2.7.x matching an exploitable production path would make this option untenable.
Option 2 — Bounded migration: Spring Boot 3 / Java 21 + H2 → PostgreSQL (recommended): Addresses F-017 (EOL upgrade), F-016 (H2-specific SQL must be rewritten), F-013 (introduce DB sequences), F-014 (introduce Flyway), and F-012 (remove JVM lock after sequences). Scope is confined to the existing Java architecture; no rewrite of business logic, no new language, no service decomposition. Estimated disruption: moderate (Jakarta EE namespace migration, test coverage build-up required for confidence). The separately authorized Java invoice-preview pilot validates the calculate() path on the target stack before committing to full migration. Reversal evidence: pilot failure on the calculate() path, or a confirmed business requirement for API decomposition that the retained monolith cannot satisfy.
Option 3 — Deeper modernisation (Kotlin / Microservice / FastAPI / ASP.NET Core): Justified by the coupling findings (F-012, F-022, F-023) and the desire to separate the invoice-preview service from batch processing. Higher delivery complexity and longer timeline. [a sentence held back] Reversal evidence: confirmed high concurrent load, security requirement for API isolation, or owner decision to separate operational concerns.
Unexecuted feasibility: H2-to-PostgreSQL schema compatibility, Spring Boot 3 migration test results, and Kotlin/FastAPI/ASP.NET Core compatibility are all unknown until executed. No feasibility checks have been run under the current execution policy.
Pilot actuals and future forecasts
| Metric | Observed pilot actual / future forecast | Value / unknown | Source run and revision | Limit |
|---|---|---|---|---|
| calculate() path: Java 21 / Spring Boot 3 compatibility | Future forecast (pilot not yet authorized) | Unknown | [held back] | Pilot scope: calculate() only; excludes save(), batch, jobs, DB migration |
| Test coverage increase (pilot) | Future forecast | Unknown | Pending pilot | Pilot adds unit tests for discount boundary and money rounding only |
| Build time (pilot) | Future forecast | Unknown | Pending pilot | Excludes integration test suite |
| SQL injection remediation (F-001) | Future forecast | Unknown — requires parameterized query refactor and test | Pending remediation work | Scope: customers() and invoices() only |
| H2 → PostgreSQL migration | Future forecast | Unknown | Pending DB migration stage and authorized run | FORMATDATETIME rewrite, schema migration, sequence introduction required |
No pilot has been executed. All metrics above are forecasts. A successful build or preview does not prove end-to-end migration, performance, security, deployment or operational readiness.
Effort, dependencies and unresolved inputs
Effort figures are not priced in this assessment stage. The effort model (effort-model.csv) will be authored in the downstream commercial stage binding actual authorized rates, currency, and the validated finding set from this report. WBS items and role assignments will be detailed in implementation-plan.md.
Monetary fields remain not priced until an authorized rate card, currency, tax/expense treatment and conversion are documented. [a sentence held back]
Key effort dependencies observable from the findings: F-018 (test suite gap) must be resolved before any migration branch can provide regression confidence; F-001 and F-003 must be remediated before production exposure regardless of migration path; F-009 (InvoiceRunJobV2 disposition) and F-010 (LegacyInvoicePrinter scan) require owner decisions before the migration scope is fixed; F-021 (backup/DR) requires a client policy decision that will determine whether Option 1 is viable as an interim step.
Decisions, operational gaps and acceptance limits
| Decision / gap ID | Owner role | Client input / action | Blocks | Evidence that closes it |
|---|---|---|---|---|
| DEC-001 | Client technical + security owner | Select migration target profile (Option 1, 2 or 3) | All WBS scoping, effort model, pilot authorization | Written owner decision |
| DEC-002 | Business/product owner | Confirm discount boundary at qty=100: >=100 or >100? (F-004) | Discount remediation, pilot test design | Written business rule decision |
| DEC-003 | Client operations + data owner | Define backup/DR policy for H2 file database or confirm DB migration timeline (F-021) | [held back] | Written backup policy or confirmed migration decision |
| DEC-004 | Client technical owner | Confirm InvoiceRunJobV2 disposition: remove, complete, or replace? (F-009) | Migration scope, job architecture refactoring | Owner decision and resulting source change |
| DEC-005 | Client security owner | Confirm whether any out-of-scope security filter currently protects /api/jobs/* (F-003) | Security finding severity and remediation scope | Confirmed security configuration evidence |
| DEC-006 | Business/product owner | Confirm whether auto-approve credit is intentional or a gap (F-008) | Credit workflow design, authorization scope | Written design decision |
| GAP-001 | Client operations owner | Confirm whether demo.schedule.enabled is ever set to true in any deployed environment (EV-031) | F-020 severity and remediation priority | Deployment configuration inventory or environment scan |
| GAP-002 | Client security owner | Commission and return results of SAST / dependency vulnerability scan (F-017, F-001) | CVE affectedness determination for Spring Boot 2.7.x and H2 | Scanner output with affected CVE list |
| GAP-003 | Client operations owner | Provide production data volume, backup retention and SLA requirements (EV-028, EV-029) | Backup design, H2 vs PostgreSQL sizing decision | Operational inventory or owner interview |
All seven pending decisions and gaps must be resolved before a production migration plan can be finalized. [a sentence held back]
Evidence disposition table
(One row per accepted evidence ID; disposition mapped means at least one final finding references this evidence ID.)
| Evidence ID | Disposition | Finding IDs | Owner | Reason |
|---|---|---|---|---|
| EV-001 | mapped | F-016, F-021 | null | null |
| EV-002 | mapped | F-020 | null | null |
| EV-003 | mapped | F-020 | null | null |
| EV-004 | mapped | F-009, F-010, F-015 | null | null |
| EV-005 | mapped | F-014 | null | null |
| EV-006 | mapped | F-014 | null | null |
| EV-007 | mapped | F-001 | null | null |
| EV-008 | mapped | F-001 | null | null |
| EV-009 | mapped | F-004 | null | null |
| EV-010 | mapped | F-015 | null | null |
| EV-011 | mapped | F-012 | null | null |
| EV-012 | mapped | F-012, F-013 | null | null |
| EV-013 | mapped | F-013 | null | null |
| EV-014 | mapped | F-009 | null | null |
| EV-015 | mapped | F-006 | null | null |
| EV-016 | mapped | F-006 | null | null |
| EV-017 | mapped | F-020 | null | null |
| EV-018 | mapped | F-002 | null | null |
| EV-019 | mapped | F-004 | null | null |
| EV-020 | mapped | F-023 | null | null |
| EV-021 | mapped | F-003 | null | null |
| EV-022 | mapped | F-016 | null | null |
| EV-023 | mapped | F-016 | null | null |
| EV-024 | mapped | F-017, F-018, F-019 | null | null |
| EV-025 | mapped | F-015, F-022 | null | null |
| EV-026 | mapped | F-005 | null | null |
| EV-027 | mapped | F-005, F-011 | null | null |
| EV-028 | mapped | F-021 | null | null |
| EV-029 | mapped | F-019, F-020 | null | null |
| EV-030 | mapped | F-009 | null | null |
| EV-031 | mapped | F-020 | null | null |
| EV-032 | mapped | F-011 | null | null |
| EV-033 | mapped | F-007 | null | null |
| EV-034 | mapped | F-008 | null | null |
| EV-035 | mapped | F-017 | null | null |
Target-specific assessment annexes
Each annex links consolidated findings from assessment.md. Assessment and planning only except the separately authorized Java invoice-preview pilot. No target implementation has been executed or inferred. For every annex the three standard subsections are: decision/options; compatibility/behavior/evidence; and delivery/rollback/open decisions.
Java 8 → Java 21
Profile: java-8-to-21
Decision and options
Assessment recommendation: proceed with Java 21 upgrade as the first mandatory step of the bounded migration path (Option 2 in `assessment.md`).
This upgrade is a prerequisite for Spring Boot 3 (which requires Java 17 minimum; Java 21 is the current LTS target). The Java 8 source level (EV-024) is the binding constraint on all modern library adoption. Three options exist for the Java upgrade alone:
- Option A — In-place Java 21 upgrade (recommended): Upgrade
java.versionto 21, update Spring Boot to 3.x (which carries the Jakarta EE namespace migration), and recompile without any business logic changes. Combine with F-015 remediation (Class.newInstance() → getDeclaredConstructor().newInstance()) and F-017 EOL resolution in the same branch. - Option B — Java 17 intermediate step: Upgrade to Java 17 first to validate build compatibility before the Spring Boot 3 Jakarta namespace migration. Adds a delivery increment but reduces the blast radius of any single upgrade step.
- Option C — Retain Java 8 (not recommended): Resolves no EOL risk; blocks all other migration targets that require Java 17+. Only viable as a short-term stabilisation hold while Option 1 security remediations are applied.
Decision trigger: F-017 (EOL), F-015 (deprecated API), F-018 (test coverage required before migration confidence). F-009 (InvoiceRunJobV2 disposition) must be resolved to confirm that the upgraded build contains no dead stub that masks a missing replacement.
Compatibility, behavior and evidence
Impacted BASE components: All Java source files in com.ingramtrials.base — Desk.java (19 KB), DeskController.java, ApiErrors.java, FallbackError.java, ScheduledJobs.java, InvoiceRunJob.java, CreditNoteJob.java, InvoiceRunJobV2.java, LegacyInvoicePrinter.java, Job.java, BaseApplication.java.
Runtime and build invariants:
| Invariant | Evidence | Result / status |
|---|---|---|
Class.newInstance() deprecated since Java 9 | EV-010 / source_config | Must replace before Java 21 compiler warnings become errors in strict mode |
javax.servlet.* imports (FallbackError.java, ApiErrors.java) | Source inspection / source_config | Spring Boot 3 migrates to jakarta.servlet.*; these imports will fail to compile on Spring Boot 3 without change |
@EnableScheduling, WebMvcConfigurer (BaseApplication.java) | EV-035 / inference | Both are present in Spring Boot 3 with compatible APIs; no breaking change expected |
| H2 2.2.224 compatibility with Java 21 | EV-024 / source_config | H2 2.2.x is tested with Java 21; no known incompatibility, but compatibility test not run |
synchronized keyword compatibility | EV-011, EV-012 / source_config | No change; synchronized remains valid in Java 21. Virtual threads (Project Loom) interact with synchronized differently — pinning risk if virtual threads are adopted; this assessment does not adopt virtual threads |
BigDecimal arithmetic behavior | EV-009, EV-011 / source_config | No behavioral change in BigDecimal arithmetic between Java 8 and 21 for the rounding modes used |
Feasibility checks: Not run. Build compilation against Java 21 and Spring Boot 3 not executed under the current execution policy. Results are unknown.
Scoped security status: F-017 under_investigation — EOL fact established; specific CVE affectedness requires a dependency scanner run which has not been performed (EV-024).
Delivery, rollback and open decisions
WBS/effort basis: Effort is not priced. Accepted plan tasks: WBS-3.1 (Jakarta namespace migration, Class.newInstance() replacement, Spring Boot 3 / Java 21 build), WBS-1.3 (baseline test suite required before migration confidence). Key tasks include: Jakarta namespace migration (javax → jakarta across all source files), Class.newInstance() replacement (F-015), Spring Boot 3 dependency resolution and test run, compilation verification, and execution of the DeskMoneyTest regression baseline. The test suite gap (F-018) is a parallel prerequisite — migration confidence requires more than one test.
Sequencing/dependencies: Java 21 upgrade must precede the H2 → PostgreSQL migration (the PostgreSQL driver for Spring Boot 3 requires JDBC 4.3+, included in the Java 21 / SB3 stack). The pilot (calculate() path) can be executed in parallel with the Java upgrade once the build compiles.
Rollback boundary: Revert the java.version property and the Spring Boot parent version in pom.xml. Keep the original source under version control; the Jakarta namespace change is reversible by reverting the branch.
Uncertainty: The Jakarta EE namespace migration surface area is limited to the servlet imports in FallbackError.java and ApiErrors.java, plus any transitive Spring dependencies. The actual impact will not be known until a compile run is executed.
Client input required: DEC-001 (migration target decision), F-009 (InvoiceRunJobV2 disposition before final source inventory is locked).
Pending decision owner: Delivery tech lead.
Java → Kotlin
Profile: java-to-kotlin
Decision and options
Assessment recommendation: not recommended as the primary migration path at this time. Kotlin migration is viable as a long-term improvement objective after bounded migration (Java 21 + Spring Boot 3) stabilises the estate.
Kotlin interop migration is justified when a team wants null-safety enforcement, coroutines, or idiomatic functional patterns, and the existing test coverage is sufficient to validate behavioral equivalence. [a sentence held back] The low test coverage makes a Kotlin rewrite high risk for behavioral regression.
- Option A — Kotlin new-code policy: New files added during migration are written in Kotlin; existing Java source is retained. Low risk; slow coverage gain. Viable after Java 21 upgrade.
- Option B — Incremental Kotlin conversion: Convert one component at a time (Job interface and wrappers first, then DeskController, then Desk). Requires Java–Kotlin interop validation at each boundary.
- Option C — Full Kotlin rewrite: Maximum Kotlin coverage; highest delivery risk given current test baseline. Not recommended without a comprehensive test suite in place first (F-018).
Decision trigger: F-018 (test suite must be built before a rewrite is safe), DEC-001 (target selection).
Compatibility, behavior and evidence
Impacted BASE components: All Java source files would be converted. Key interop invariants:
| Invariant | Evidence | Result / status |
|---|---|---|
synchronized keyword | EV-011, EV-012 / source_config | Kotlin uses @Synchronized annotation; behavioral equivalence in single-JVM; same virtual-thread caution applies |
BigDecimal arithmetic (money rounding, GST, discount) | EV-009, EV-032 / source_config | Kotlin delegates to Java BigDecimal; no behavioral change expected but must be confirmed by test |
| Reflective job dispatch (Class.forName) | EV-010 / source_config | Kotlin supports reflection via KClass and Java Class; existing reflection pattern is replaceable with Kotlin idioms |
@PostConstruct, @Scheduled, @ControllerAdvice | EV-005, EV-017, source inspection | Spring annotations work identically in Kotlin; no compatibility barrier |
| Null safety for String SQL parameters (EV-007, EV-008) | EV-007, EV-008 / source_config | Kotlin null-safety would surface the SQL injection defect as a platform concern but does not fix it; F-001 remediation is independent |
| Decimal serialization in REST responses | EV-019 / source_config | Jackson BigDecimal serialization behavior is unchanged; must be confirmed by test |
Feasibility checks: Not run. Kotlin compilation and interop tests not executed.
Delivery, rollback and open decisions
WBS/effort basis: Effort is not priced. Accepted plan tasks: WBS-1.3 (baseline test suite — required before Kotlin conversion is safe), WBS-3.10 (tech lead integration review, Wave 3 — covers all converted components). Kotlin migration effort is higher than Java 21 in-place upgrade due to the syntactic rewrite scope. Baseline test suite (F-018) must be authored before migration begins.
Increment boundary: Job interface and wrappers are the smallest safe first increment (minimal logic, clear interface boundary). Desk.java is the highest-risk last increment due to its financial logic density.
Rollback: Kotlin files can coexist with Java on the same classpath; reverting a Kotlin file to its Java equivalent is always possible while the original is retained under version control.
Client input required: DEC-001 (target selection), F-018 (test suite build before conversion).
Pending decision owner: Client technical owner and delivery tech lead.
JavaScript → TypeScript
Profile: javascript-to-typescript
Decision and options
Assessment recommendation: TypeScript migration for desk.js is a low-risk quality improvement, recommended as a parallel track to the backend migration rather than a blocker.
The browser SPA consists of one JavaScript file (desk.js, 6 190 bytes) with no build toolchain, no module system and all API calls using the Fetch API with hardcoded paths (EV-020). TypeScript adoption would add type-checked API contract definitions and surface the hardcoded /api base path as an explicit configuration concern (F-023).
- Option A — TypeScript with a build step (recommended): Introduce a minimal tsconfig.json and compile desk.ts to desk.js. No runtime change; browser behavior is identical. Type definitions for API request/response shapes would surface the discount boundary (F-004) and line-limit (F-007) constraints at the IDE level.
- Option B — Retain JavaScript (acceptable for short term): No new risk introduced; F-023 and F-007 remain unaddressed at the UI layer.
- Option C — Frontend framework adoption (out of scope): Introducing React/Vue/Angular is a larger scope change not implied by the current estate and not recommended without a client UI roadmap decision.
Decision trigger: F-023 (hardcoded base URL), F-007 (no client-side line-limit validation), DEC-001.
Compatibility, behavior and evidence
Impacted frontend modules: desk.js only. index.html (static markup, 4 885 bytes) and style.css (4 998 bytes) are unaffected.
| Invariant | Evidence | Result / status |
|---|---|---|
| Browser API compatibility | EV-020 / source_config | Fetch API and standard DOM APIs are TypeScript-compatible without polyfills |
| API base path constraint | EV-020 / source_config | F-023: same-origin constraint must be documented or resolved before TS migration introduces a typed API_BASE_URL |
| Hardcoded paths in fetch calls | EV-020, EV-021 / source_config | All 10+ fetch calls in desk.js use '/api' prefix; TypeScript migration is the opportunity to introduce an API_BASE constant |
| No module system | EV-020 / source_config | desk.js uses no import/export; TypeScript migration requires a module bundler (esbuild, Vite) or inline script pattern |
| Spring Boot static resource serving | EV-003, EV-035 / source_config, inference | Spring Boot serves static files from src/main/resources/static/; compiled desk.js must replace or augment the original |
Feasibility checks: Not run. TypeScript compilation and build toolchain not configured or tested.
Delivery, rollback and open decisions
WBS/effort basis: Effort is not priced. Accepted plan tasks: WBS-3.5 (remove JVM lock, introduce JobContext, resolve /api base path constraint — F-023), WBS-4.2 (client-side line-count validation in desk.js — F-007). Scope: rename desk.js → desk.ts, add tsconfig.json, add a build step to pom.xml or a separate npm build, type the API shapes, resolve F-023 (configurable base URL). Small scope; low delivery risk.
Module order: Migrate desk.js as a single unit; no incremental file-by-file order is needed.
Build rollback: Retain the original desk.js alongside the TypeScript source. If the build step fails, Spring Boot can serve the pre-compiled JavaScript file unchanged.
Client input required: DEC-001, deployment topology decision (required to resolve F-023).
Pending decision owner: Delivery tech lead (frontend competency confirmation required).
H2 → PostgreSQL
Profile: h2-to-postgresql
Decision and options
Assessment recommendation: H2 → PostgreSQL migration is required for any production deployment. It is the highest-impact structural change in the bounded migration path and must be sequenced after the Java 21 / Spring Boot 3 upgrade.
H2 is a development and demo database. The financial data (invoices, orders, credit notes) requires a production-grade RDBMS with WAL backup, point-in-time recovery, and concurrent-access semantics. Three database-layer options exist:
- Option A — PostgreSQL (recommended): Strongest open-source production support, native JSON, SERIAL/SEQUENCE identity, TO_CHAR() replaces FORMATDATETIME (F-016), schema migration via Flyway aligns with F-014 remediation.
- Option B — MySQL/MariaDB: Viable alternative if the client has an existing MySQL estate; requires STR_TO_DATE() replacement for FORMATDATETIME and separate sequence strategy.
- Option C — Retain H2 with backup regime (short-term only): Documented in assessment.md as a stabilisation step; does not resolve F-016 (H2-specific SQL), F-013 (sequence-based IDs), or F-021 (backup/DR gap) long-term.
Decision trigger: F-016 (H2 FORMATDATETIME blocker), F-013 (COALESCE ID pattern), F-014 (inline DDL — must be migrated to Flyway scripts), F-021 (backup/DR policy), F-012 (synchronized lock removal depends on DB sequences).
Compatibility, behavior and evidence
Impacted components: Desk.java (all JdbcTemplate SQL), application.properties (datasource config), @PostConstruct DDL (EV-006), all reporting queries (EV-022, EV-023), ID generation (EV-013, EV-012).
| Invariant | Evidence | Result / status |
|---|---|---|
| FORMATDATETIME → TO_CHAR | EV-022, EV-023 / source_config | Four confirmed uses in monthly() and annual(); must be replaced before cutover — not run |
| COALESCE MAX+1 → SERIAL/SEQUENCE | EV-012, EV-013 / source_config | Invoice ID and credit_request ID generation must be replaced with PostgreSQL SERIAL or SEQUENCE columns; schema migration script required |
| CREATE TABLE IF NOT EXISTS DDL | EV-006 / source_config | All eight DDL statements must be extracted into Flyway V1__init.sql migration; @PostConstruct setup() retains only application-level initialization |
| DB_CLOSE_ON_EXIT=FALSE H2 property | EV-001 / source_config | Remove H2-specific JDBC URL properties; replace with PostgreSQL JDBC URL and connection pool config |
| H2 in-memory → file test behavior | EV-001, EV-024 / source_config | Integration tests currently have no H2 in-memory test config; PostgreSQL migration introduces a test-container dependency for integration tests |
| SQL dialect (string functions, date types) | EV-022, EV-023 / source_config | No other H2-specific functions confirmed in inspected scope beyond FORMATDATETIME; additional dialect differences possible in uninspected query paths |
| Invoice reconciliation invariant | EV-009, EV-026, EV-027 / source_config | The discount off-by-one (F-004) and batch atomicity gap (F-005) must be remediated before or concurrent with the DB migration to avoid persisting incorrect data to PostgreSQL |
Feasibility checks: Not run. PostgreSQL dialect test, Flyway migration script execution, and connection pool configuration not tested.
Delivery, rollback and open decisions
WBS/effort basis: Effort is not priced. Accepted plan tasks: WBS-3.2 (Flyway DDL extraction — F-014), WBS-3.3 (FORMATDATETIME → TO_CHAR replacement and COALESCE → SERIAL/SEQUENCE migration — F-013, F-016). Key tasks: Flyway DDL extraction (F-014), FORMATDATETIME replacement in all queries (F-016), SERIAL/SEQUENCE ID migration (F-013), application.properties datasource reconfiguration, PostgreSQL connection pool (HikariCP, default in Spring Boot 3), integration test update with Testcontainers, dual-run verification.
[a sentence held back] No data migration tooling is present in the current source; client data volume (EV-028, EV-029) is unknown and must be confirmed before sizing the cutover window.
Backup/restore rollback: H2 file backup must be captured before cutover. PostgreSQL rollback requires a point-in-time recovery snapshot; this depends on the DEC-003 backup policy decision.
Volume/environment gaps: GAP-003 (data volume and retention requirements) and DEC-003 (backup/DR policy) are unresolved. These must be closed before the DB migration can be scoped.
Client input required: DEC-003 (backup/DR policy), GAP-003 (data volume), DEC-001 (migration target confirmation).
Pending decision owner: Client operations and data owner; delivery tech lead.
Monolith → Preview microservice
Profile: monolith-to-preview-service
Decision and options
Assessment recommendation: microservice extraction of the invoice-preview path is a viable future option, not a recommended immediate action. The bounded migration (Option 2) should stabilise the estate before decomposition is attempted.
The only clean extraction boundary evidenced in the current source is Desk.calculate() (the preview path), which has no database write, no job dependency and no synchronized lock requirement. This is the scope of the separately authorized Java invoice-preview pilot.
- Option A — Extract preview service (aligned with pilot scope): Separate
Desk.calculate()into a stateless preview microservice. The pilot validates this boundary. The remaining monolith retains save(), batch, credit, reporting and job dispatch. - Option B — Retain monolith, expose preview as an internal module: Keep the application architecture unchanged but isolate the calculate() path behind a module boundary. Lower deployment complexity; no service boundary crossing.
- Option C — Full decomposition (not recommended at this stage): Decompose into invoice-service, batch-service, credit-service and reporting-service. Requires resolving F-013 (shared ID generation), F-022 (Job interface coupling), F-012 (synchronized lock), F-020 (schedule ownership), and F-021 (data ownership per service). High delivery complexity for an estate of this size.
Decision trigger: F-012 (synchronized lock blocks scaling), F-013 (ID generation must be resolved), F-022 (Job coupling), DEC-001, pilot completion.
Compatibility, behavior and evidence
Boundary/call graph evidence:
| Boundary | Evidence | Status |
|---|---|---|
| calculate() has no DB write | EV-009, EV-019 / source_config | Confirmed from source — calculate() returns a result map without persisting |
| calculate() uses synchronized lock? | EV-011 / source_config | No — save() is synchronized, not calculate(). Preview path is stateless and lock-free |
| API contract: POST /api/invoices/preview | EV-019 / source_config | Route maps to Desk.calculate(body, true); request/response shape is JSON; no external contract (F-019) |
| Authentication on preview endpoint | EV-021, EV-003 / source_config | No auth guard visible in inspected source; same concern as F-003 |
| Idempotency | EV-019 / source_config | Preview is read-only and stateless; fully idempotent |
| Data ownership | EV-001 / source_config | Preview service needs product and customer data (read-only); currently co-located in H2; shared DB or read-replica required if decomposed |
Feasibility checks: Not run. Service boundary test, inter-service contract validation and observability not configured.
Delivery, rollback and open decisions
WBS/effort basis: Effort is not priced. Accepted plan tasks: WBS-2.1 (pilot — extract Desk.calculate() to Java 21 / Spring Boot 3 preview endpoint, auth guard, discount-boundary tests), WBS-3.5 (remove JVM synchronized lock and introduce JobContext interface — F-012, F-022, prerequisite for full decomposition). Key tasks for pilot scope: extract calculate() to new Spring Boot module, expose POST /api/invoices/preview, add discount-boundary tests (F-004 fix), add authentication guard (F-003), validate against Java 21 / Spring Boot 3. Full decomposition adds: API gateway configuration, service-to-service auth, shared data access pattern, distributed transaction strategy (not needed for preview-only service).
Strangler/route sequence: Pilot extracts calculate() first. Other routes remain in the monolith. Route rollback is a Spring Boot proxy/redirect from the monolith to the preview service, reversible by removing the redirect.
Route rollback: Restore original /api/invoices/preview route in the monolith if the preview service fails health checks. No data migration is involved.
Operational ownership gaps: Service discovery, health check monitoring, container orchestration and deployment pipeline for the preview service are not configured in the current source. These are client operational decisions.
Client input required: DEC-001, DEC-005 (auth coverage confirmation), pilot authorization.
Pending decision owner: Client architecture owner and delivery tech lead.
Java → Python / FastAPI
Profile: java-to-fastapi
Decision and options
Assessment recommendation: FastAPI rewrite is not recommended as the primary migration path for this estate. It is viable for a greenfield preview microservice if the client's platform preference or team expertise strongly favors Python, but it requires a full rewrite of all business logic with no automated translation path.
The current estate is tightly coupled in Desk.java (financial math, SQL, job dispatch, scheduling, seed data management in a single class). A FastAPI rewrite would need to faithfully reproduce every discount boundary, GST calculation and invoice-line constraint — a high behavioral risk given the current test coverage (F-018, one test).
- Option A — FastAPI for preview service only (limited scope): Implement POST /api/invoices/preview in FastAPI with Pydantic schemas. Scope aligns with the pilot boundary. Requires confirming the Decimal/BigDecimal rounding equivalence.
- Option B — Full FastAPI rewrite: All endpoints, batch jobs, reporting and scheduling reimplemented in Python. High delivery risk; no behavioral test baseline.
- Option C — Retain Java (recommended for full estate): FastAPI adds Python operating-model overhead (deployment, async patterns, Pydantic v2) without evidence that the performance or productivity gain justifies the risk for this size of application.
Decision trigger: F-018 (test suite required before any rewrite), DEC-001, client platform preference.
Compatibility, behavior and evidence
Impacted components: All endpoints and job paths.
| Invariant | Evidence | Status |
|---|---|---|
| BigDecimal → Python Decimal equivalence | EV-009, EV-032 / source_config | Java BigDecimal with HALF_UP rounding (Desk.money()) must map to Python Decimal with equivalent rounding; behavior test required |
| GST 9% calculation and scale | EV-027, EV-032 / source_config | Python Decimal division behavior differs subtly from BigDecimal; must be confirmed by test |
| Discount boundary at qty=100/>=100/>100 | EV-009 / source_config | [held back] |
| Async batch jobs (invoiceBatch, creditBatch) | EV-026, EV-027 / source_config | Python async or background task patterns differ from Spring @Scheduled; transaction semantics must be reproduced |
| SQL → SQLAlchemy / asyncpg | EV-007, EV-008, EV-022, EV-023 / source_config | F-001 SQL injection must not be reproduced; parameterized queries are default in SQLAlchemy ORM |
| Pydantic noncoercing schemas | EV-033, EV-009 / source_config | [held back] |
Feasibility checks: Not run. FastAPI compilation, Decimal behavior and async batch patterns not tested.
Delivery, rollback and open decisions
WBS/effort basis: Effort is not priced. Accepted plan tasks: WBS-1.3 (baseline test suite — required before any rewrite to validate behavioral equivalence), WBS-3.11 (full regression run on Java 21 / Spring Boot 3 / PostgreSQL — establishes the passing baseline that a FastAPI rewrite must match). Full rewrite scope is larger than bounded Java migration. Python operating-model (virtual environment, dependency management, container image, async framework) adds operational overhead not present in the current Spring Boot JAR.
Route/data sequence: Preview endpoint first (lowest state risk); batch/job/reporting endpoints require async pattern design; database migration is the same prerequisite as Option 2 regardless of language.
Rollback: Java and FastAPI can coexist behind a proxy during transition; full rollback requires maintaining the Spring Boot application in parallel.
Python operating-model gaps: No Python deployment configuration, CI/CD pipeline or container image exists in the current source. These require client operational investment.
Client input required: DEC-001, client platform preference, F-018 (test suite before rewrite).
Pending decision owner: Client technical owner and delivery tech lead.
Java → C# / ASP.NET Core
Profile: java-to-aspnet-core
Decision and options
Assessment recommendation: ASP.NET Core rewrite is not recommended as the primary migration path for this estate. It is viable if the client's enterprise standard platform is .NET, but it requires a full rewrite with the same behavioral risk profile as the FastAPI option given the current test coverage (F-018).
[a sentence held back] However, the BigDecimal financial math, the SQL injection remediation scope and the H2-to-PostgreSQL migration are independent concerns that apply regardless of the target language.
- Option A — ASP.NET Core Web API for preview service only: Implement POST /api/invoices/preview in ASP.NET Core. Aligns with pilot boundary. Requires Decimal/JSON binding validation.
- Option B — Full ASP.NET Core rewrite: All endpoints, IHostedService for background jobs, EF Core or Dapper for database access. High delivery risk without a test baseline.
- Option C — Retain Java (recommended for full estate): Same rationale as FastAPI option; .NET operating-model adds platform overhead not justified by the current estate complexity.
Decision trigger: F-018 (test suite required before rewrite), DEC-001, client platform standard.
Compatibility, behavior and evidence
Impacted components: All endpoints and job paths.
| Invariant | Evidence | Status |
|---|---|---|
| decimal/JSON binding (C# decimal vs Java BigDecimal) | EV-009, EV-032 / source_config | C# decimal has 28-29 significant digits vs Java BigDecimal arbitrary precision; rounding behavior must be confirmed for HALF_UP equivalent (MidpointRounding.AwayFromZero) |
| Model binding overflow | EV-033 / source_config | ASP.NET Core model binding with strict decimal validation required to reproduce the 1-50 line limit and scale checks (F-007) |
| Middleware exception handling | Source inspection (ApiErrors.java, FallbackError.java) / source_config | Spring @ControllerAdvice maps to ASP.NET Core Exception Middleware or ProblemDetails; functional equivalence must be tested |
| Background services (IHostedService) for scheduling | EV-017, EV-002 / source_config | Spring @Scheduled maps to IHostedService with a timer; demo.schedule.enabled guard must be reproduced |
| EF Core / Dapper for SQL | EV-007, EV-008, EV-022, EV-023 / source_config | F-001 SQL injection must not be reproduced; parameterized queries are default in EF Core; FORMATDATETIME must be replaced with EF LINQ date functions |
| Static file serving for SPA | EV-020, EV-003 / source_config | ASP.NET Core UseStaticFiles() serves desk.js; same-origin constraint (F-023) applies unchanged |
| Hosting model (.NET vs embedded Tomcat) | EV-035 / inference | Spring Boot embedded Tomcat maps to Kestrel in ASP.NET Core; Kestrel binding and port configuration replaces server.address/server.port |
Feasibility checks: Not run. ASP.NET Core compilation, Decimal rounding and middleware tests not executed.
Delivery, rollback and open decisions
WBS/effort basis: Effort is not priced. Accepted plan tasks: WBS-1.3 (baseline test suite — required before any rewrite to validate behavioral equivalence), WBS-3.11 (full regression run on Java 21 / Spring Boot 3 / PostgreSQL — establishes the passing baseline that an ASP.NET Core rewrite must match). Full rewrite scope similar to FastAPI option. .NET operating-model (SDK, runtime, container image, IIS/Kestrel deployment) adds operational overhead not present in the Spring Boot JAR.
Hidden here: a sentence our checks hold back.
Rollback: Java and ASP.NET Core can coexist behind a reverse proxy; full rollback requires maintaining the Spring Boot application in parallel.
.NET operating-model gaps: No .NET project file, SDK configuration or container image exists in the current source. Client .NET platform investment is required.
Client input required: DEC-001, client platform standard decision, F-018 (test suite before rewrite).
Pending decision owner: Client technical owner and delivery tech lead.


