Where the rules actually live
Across 2,400 .fmb files we’ve analyzed, WHEN-VALIDATE-ITEM triggers account for 38% of all embedded PL/SQL by line count. They’re the single largest bucket of business logic in most Oracle Forms applications — more than all form-level triggers, package bodies, and library units combined. An insurance carrier we worked with had 9,700 of them in a policy administration system first deployed in 2001.
Migrations live or die on how these triggers get translated.
What WHEN-VALIDATE-ITEM is supposed to do
The trigger fires when an item loses focus and its value has changed. Its job is to decide whether the new value is acceptable. If not, it raises FORM_TRIGGER_FAILURE, which returns focus to the item and displays a message. The rules vary — range checks, cross-field dependencies, database lookups, regulatory limits.
-- Typical WHEN-VALIDATE-ITEM
IF :POLICY.COVERAGE_AMOUNT > 1000000
AND :POLICY.UNDERWRITER_LEVEL < 3 THEN
MESSAGE('Coverage above 1M requires senior underwriter');
RAISE FORM_TRIGGER_FAILURE;
END IF;
That four-line example touches two form items, a hardcoded threshold, and a display message. It’s representative. The median WHEN-VALIDATE-ITEM trigger in our sample is 11 lines. The 95th percentile is 84.
The translation target
Our target shape is a pure TypeScript validator function that takes the current form state and returns either null or an error object. No side effects, no DOM access, no direct database calls from the validator itself.
export const validateCoverageAmount: Validator<PolicyForm> = (state) => {
if (state.coverageAmount > 1_000_000 && state.underwriterLevel < 3) {
return { field: "coverageAmount",
message: "Coverage above 1M requires senior underwriter" };
}
return null;
};
Database lookups move to async validators that call a generated REST endpoint. The validator stays pure; the endpoint owns the data access. This split is the single most important decision in the translation — it’s what makes the rules testable, cacheable, and auditable.
Handling the messy cases
Not every trigger is four clean lines. We’ve catalogued five patterns that resist naive translation:
- Implicit commits. Triggers that call
COMMITmid-validation. These get refactored into explicit save steps. - Global variables. References to
:GLOBAL.xyzthat store session state across forms. These become a typed session store. - DO_KEY calls. Triggers that re-trigger navigation events. These become explicit state transitions.
- Dynamic SQL.
FORMS_DDLandEXEC_SQLcalls. These get flagged for human review — about 6% of triggers land here. - Cross-form references. Reading items from another open form. These become session-scoped context objects.
Roughly 91% of WHEN-VALIDATE-ITEM triggers fall into clean patterns that translate automatically. The remaining 9% need review. Knowing which 9% before the project starts is the difference between a predictable timeline and a quarterly slip.
Preserving semantics the compiler can’t see
The hardest rules are the ones that depend on Oracle Forms’ execution model. A WHEN-VALIDATE-ITEM only fires when the item has changed — not on every save, not on query results, not on programmatic assignment. Getting this wrong means validators fire too often and break workflows that used to work.
We mirror the Forms semantics explicitly. The validator runtime tracks per-field dirty state, suppresses validation during query population, and honors the original trigger hierarchy (item, block, form). The translated rule is identical; the runtime that invokes it is what matches behavior.
Testing the translation
Every migrated validator gets two tests automatically: one generated from the original PL/SQL control flow, and one captured from production traffic against the legacy system. The second matters more. We replay six months of real form submissions through both the old and new validators and compare outcomes. Any divergence is a defect.
On the last four projects, this replay caught between 14 and 71 defects per 1,000 triggers — almost all in the messy-case patterns above.
The takeaway
WHEN-VALIDATE-ITEM is where 25 years of institutional knowledge lives. Translating it to TypeScript is not a syntactic exercise — it’s a semantic one, and the semantics depend on Forms’ execution model as much as on the code itself. The migrations that preserve business rules cleanly are the ones that split pure validation from data access, mirror the original execution model, and replay real traffic before cutover.