PL/SQL and Oracle Forms standards
Criteria verified as of August 2026. Re-verify on the web before committing to anything (§8).
1. Scope and triggers
Business PL/SQL code (packages, procedures, functions, triggers) and Oracle Forms/Reports
applications. Triggers: .pks/.pkb, CREATE OR REPLACE PACKAGE, BULK COLLECT, FORALL,
EXECUTE IMMEDIATE, DBMS_SQL, WHEN OTHERS, PRAGMA AUTONOMOUS_TRANSACTION, AUTHID,
utPLSQL, .fmb/.fmx/.pll/.rdf, Forms Builder, WebUtil, migration to APEX.
The axis: these are two things with different destinies and they are always mentioned together.
Separate them before deciding anything.
- PL/SQL is alive and is the foundation of a great deal of business. It is not a language in
retreat: it compiles inside an engine with support well into the 2030s (Oracle Database 19c:
Premier until Dec 2029, Extended until Dec 2032; 26ai, LTS with GA Oct 2025, Premier
until Dec 2031 — verified in the PDF Oracle Technology Products — Oracle Lifetime Support
Policy, Effective Date: May 1, 2026). Writing new PL/SQL in 2026 can be the right decision.
- Oracle Forms is what has to be got out of there — but not because it is unsupported, which is
the usual assumption and is false. Datum verified by reading the PDF Oracle Fusion Middleware —
Oracle Lifetime Support Policy, Effective Date: April 13, 2026: Fusion Middleware 14.1.x (GA
Dec 2024), whose table footnote explicitly includes "Forms and Reports", has Premier Support
until Dec 2030, Extended until Dec 2033 and indefinite Sustaining. What does press is the
previous version: FMW 12c (12.2.x), where the vast majority of the estate lives, has Premier until
Dec 2026 and Extended until Dec 2027. And 11gR2 (11.1.2.x) has been out since Dec 2016 (Premier)
/ Dec 2018 (Extended).
- Oracle Reports is another case: it is deprecated since 12.2.1.3.0, declared a terminal
release, and although it is still packaged with 14.1.2 it receives no new functionality, only
critical fixes and stack compatibility; the route Oracle points to is Oracle Analytics Publisher.
Operational conclusion: the Forms clock is not "it is dead", it is "your 12.2.x leaves Premier in
Dec 2026". And the real argument for leaving is not the calendar, but that the client platform
—Java applet, then Java Web Start, JRE dependencies on the desktop— ages faster than the product and
that the labour market of people who know how to maintain it has dried up.
Not applicable: oracle-dba-standards and sql-standards (already written) own the Oracle
engine (instance, storage, RAC, backup, patching, execution plans, indexes, engine licensing) and
the SQL language; here PL/SQL as a program language and Forms as an application layer,
with data-platform-standards and data-warehouse-modeling-standards for the analytical side.
legacy-modernization-standards is the umbrella of the block —and
migration-projects-standards the execution of the cutover: rehearsal, window, coexistence, data
reconciliation, rollback and shutdown of the source— and
enterprise-architecture-standards (already written) provides the inventory, the TIME model and
the "R"s —here what each option implies technically—, with refactoring-tech-debt-standards and
testing-qa-standards (already written: strangler fig, characterisation without tests),
project-management-standards, tech-leadership-standards, cicd-standards and
git-workflow-standards. If the destination is Java, jvm-spring-standards wins (already
written: modern Java and Spring are theirs and are the usual destination); for a separate
front-end, frontend-frameworks-standards and api-design-standards.
appsec-standards and vulnerability-management-standards (already written) provide methodology
and triage; here only the concrete PL/SQL sinks. Sister skills of the legacy block —they share the
"legacy" label and little else—: abap-sap-standards, mainframe-zos-cobol-standards,
ibm-i-rpg-standards, jsp-struts-standards, coldfusion-standards, vb6-standards.
2. Default decisions
Verify on the web before pinning it (§8): FMW and engine support dates, and the APEX version.
| Decision |
By default |
Note |
| Unit of code |
Package (PACKAGE/PACKAGE BODY) |
Standalone procedures only for trivial jobs |
| SQL in PL/SQL |
Static whenever possible |
It is checked at compile time and records the dependency |
| Unavoidable dynamic |
EXECUTE IMMEDIATE with USING + DBMS_ASSERT for identifiers |
§5; never concatenate values |
| Volume |
BULK COLLECT with LIMIT + FORALL |
The row-by-row loop is performance defect no. 1 |
| Exceptions |
Catch what is expected and named; rethrow the rest |
WHEN OTHERS THEN NULL: forbidden (§3) |
| Rights |
AUTHID DEFINER by default, consciously |
CURRENT_USER when the caller must supply their own permissions |
| Tests |
utPLSQL (Apache-2.0, verified from the raw file) |
§4 |
| New Forms |
None: no new .fmb modules are created |
New functionality, outside (§7) |
| Frequent destination |
APEX if the logic stays in the database; Java/web if you have to get out of it |
§7 |
3. PL/SQL: conventions that are required
- Package as the unit of module: minimal specification (only what is public), body with the rest.
The specification is the contract: changing it invalidates all its dependents, the body does not.
%TYPE and %ROWTYPE always instead of literal types: the code follows the model when the
column changes size.
- No repeated loose SQL: access to a table is concentrated in its package. If the same
SELECT appears in eleven places, eleven places will break with the next model change.
- Exceptions: every
EXCEPTION catches named exceptions (NO_DATA_FOUND,
DUP_VAL_ON_INDEX, or your own with EXCEPTION_INIT) and does something real with them. To
propagate with context: RAISE_APPLICATION_ERROR(-20xxx, ...) preserving
SQLERRM/DBMS_UTILITY.FORMAT_ERROR_BACKTRACE, or a bare RAISE.
WHEN OTHERS THEN NULL is the most damaging antipattern in this ecosystem, and it deserves an
explanation because it gets written out of habit: it turns a failure into a silent success. The
transaction carries on, the nightly process "finishes fine", the record was not inserted, nobody
finds out until the accounts are reconciled months later, and by then there is neither a trace nor
any way to know how many rows were missing. It is not bad style: it is undetectable data loss.
Its cousin, the WHEN OTHERS that does DBMS_OUTPUT.PUT_LINE and does not rethrow, is the same
thing with more steps. Rule: a WHEN OTHERS that does not end in RAISE (or in
RAISE_APPLICATION_ERROR) is a review error; the only legitimate use is to log and rethrow.
- Transactions:
COMMIT/ROLLBACK are decided by whoever starts the unit of work, not by the
service procedure that is called from three places; a COMMIT buried in a utility function breaks
the atomicity of all its callers.
PRAGMA AUTONOMOUS_TRANSACTION only for what must survive the ROLLBACK: auditing and error
logging. Using it "so the lock stops bothering me" or to avoid a trigger's mutation hides the
error and produces inconsistent data, aggravated by the fact that the autonomous transaction does
not see the uncommitted changes of the main one and can deadlock against itself.
- Triggers: the fewest possible. Business logic spread across triggers is impossible to read and
to debug: the execution order is not evident and the effect fires where nobody expects it. For
auditing,
FLASHBACK/audit columns or a compound trigger is better than a cascade.
- Dependencies and invalidation: every object records what it depends on; a DDL invalidates its
dependents and recompilation arrives on the first execution (or fails). Operational consequences:
deploy specification changes with the application stopped or with the package free of active
sessions, because changing a package with state in use causes ORA-04068 in live sessions;
review
USER_OBJECTS looking for INVALID after every deployment and treat it as a failure,
not as noise; and avoid package-level state (global variables) unless there is a justified need.
- Performance: SQL↔PL/SQL context switches in a loop are the dominant cost.
BULK COLLECT
always with LIMIT (an unbounded collection loads the whole table into session memory),
FORALL with SAVE EXCEPTIONS and review of SQL%BULK_EXCEPTIONS. Measure with DBMS_HPROF and
PL/Scope, not by intuition; the execution plan of the queries belongs to oracle-dba-standards.
Business logic in the database, as an architecture decision. In favour, and it is serious: it is
where the data is (zero network latency per operation, sets processed in the engine), integrity is
guaranteed even with several applications and an ETL writing, the transaction is natural, and the code
survives three generations of front-end framework —there is PL/SQL from the nineties serving today—.
Against, and this is serious too: it ties you to the engine (and to its bill), modern engineering
tooling arrives late or badly (tests, dependencies, CI, review), it scales only vertically with the
database server, it mixes the application deployment with that of the data, and the labour market gets
narrower every year. Criteria: integrity rules and bulk data processes, inside; orchestration,
third-party integration, presentation and logic that changes at the pace of the business, outside. And
the decision is taken once and written down (ADR), because the expensive failure is logic
duplicated in both places, diverging in silence.
4. Quality and testing
- utPLSQL as the test framework: verified by reading the raw file, Apache-2.0 licence
(
LICENSE present in both main and develop). Annotations (--%suite, --%test), output in
formats consumable by CI (JUnit/SonarQube), and per-block coverage. It is the tool that makes it
possible to treat PL/SQL as real code.
- Test what hurts: monetary calculations, business rules, date cut-offs, and the error paths
—what happens when the row does not exist, when the
FORALL fails halfway, when two sessions touch
the same thing—. Test data created and destroyed by the suite itself; a suite that depends on the
content of the development schema is a status report, not a test.
- Static analysis: compiler warnings enabled and treated as errors (
PLSQL_WARNINGS), which
detect dead code, WHEN OTHERS without RAISE and misused parameters; PL/Scope for dependencies
and identifiers. There are commercial and free linters for PL/SQL: verify status and licence before
adopting one (§8).
- CI: the schema is built from the repository (the source wins, not the database), it is
deployed to an ephemeral schema, the suite is run and the build fails if any object is left
INVALID. That last gate is cheap and catches half the deployment incidents.
5. Security
- Bind variables: a security requirement and a performance one at the same time. Concatenating
values into a statement is SQL injection and additionally generates a different statement per value,
which fills the shared pool and causes a hard parse on every execution. With
USING neither
happens. There is no case in which concatenating a value is the right option.
- What does not accept a bind (table, column, schema names; dynamic
ORDER BY clauses) is
validated with DBMS_ASSERT (SIMPLE_SQL_NAME, SQL_OBJECT_NAME, SCHEMA_NAME) and
against a closed allowlist. DBMS_ASSERT on its own does not authorise: it checks shape, not
permission.
AUTHID is a security decision, not a detail: DEFINER (the default) executes with the owner's
privileges, so a DEFINER procedure with injectable dynamic SQL hands the owner's privileges to
the attacker — it is the classic escalation pattern in Oracle. CURRENT_USER (invoker rights)
forces the caller to have their own permissions and limits the damage, in exchange for demanding a
properly built privilege model. Rule: public API in DEFINER, minimal and with no dynamic SQL;
anything with dynamic SQL, reviewed line by line.
- Exposed database surface: permissions are granted on packages, not on tables — the
application should not have direct
SELECT/INSERT on the business tables—. Specific roles, no
GRANT ... TO PUBLIC, review of dangerous packages (UTL_FILE, UTL_HTTP, UTL_SMTP,
DBMS_SCHEDULER, DBMS_JAVA) and of the network ACLs: an accessible UTL_HTTP turns the
database into an internal HTTP client, that is, SSRF from the heart of the system.
- If there is ORDS/APEX, the database starts serving HTTP: it is a new trust boundary. The
parsing schema with the fewest privileges, the REST endpoints authenticated and authorised one by
one, and the APEX administration console never reachable from the internet.
- Forms: credentials on the
frmweb command line/in the .fmb, EXEC_SQL with literals, and
authorisation logic implemented only in the client (hiding a button is not a control) are the
three usual findings. Authorisation is checked in the database, always.
- Patching: Oracle's quarterly Critical Patch Updates cover the database and Fusion Middleware;
a Forms 12.2.x with no CPU applied is a legacy Java server with no patches. Calendar and triage, in
vulnerability-management-standards.
6. Exit from Forms and Reports
Four real routes, with their criteria:
- Oracle APEX — the most frequent destination when the logic already lives in PL/SQL.
Verified: it is a no-additional-cost feature of the Oracle database (all editions,
including Free and Autonomous), not a separate product that is licensed; it requires ORDS;
version 26.1, released on 14 May 2026 (there was no 25.x), with release support until
30 Nov 2027 and ~18 months per version. The key dependency, stated plainly: APEX only runs on
an Oracle engine. Migrating to APEX does not get you off the Oracle bill —it ties you in
further—, and that is exactly why Oracle pushes it as the exit from Forms. It is a great option if
you are going to stay with Oracle for ten years; it is the worst one if the goal was to leave it.
- Rewrite to Java/web (or to whatever stack the house maintains): the only route that cuts the
dependency. Expensive, and the quality of the result is governed by
jvm-spring-standards, not by
this one.
- Modernise Forms in situ (move up to 14c, remove the applet, integrate it behind a reverse
proxy): it buys 5-7 years of calendar, it does not solve the staffing or the architecture problem.
It is a legitimate "invest the minimum" decision for a stable system with a known retirement date.
- Automatic conversion tools (Forms→APEX, Forms→Java): they are useful for the inventory and for
the scaffolding —screens, LOVs, tables—, and they produce generated code that has to be redone:
they translate the structure of a fat client, with its event model and its per-block state, into a
stateless environment, and what comes out is neither idiomatic nor maintainable. Use them as an
accelerator for the mechanical 60%, never as the final delivery, and budget the rewrite of the
rest.
And before all four: PL/SQL stays. The expensive mistake in these migrations is rewriting the
business logic that is already in packages and works too. Forms (presentation and navigation) is
separated from PL/SQL (business), the latter is kept and the former is replaced.
7. Prohibitions
- ❌ FORBIDDEN
WHEN OTHERS THEN NULL, and any WHEN OTHERS that does not rethrow.
- ❌ FORBIDDEN to concatenate values in dynamic SQL. No exception for "it is a number".
- ❌ FORBIDDEN dynamic SQL with identifiers without
DBMS_ASSERT and an allowlist.
- ❌ FORBIDDEN to process row by row what a set statement or a
FORALL resolves.
- ❌ FORBIDDEN
BULK COLLECT without LIMIT over tables of unbounded size.
- ❌ FORBIDDEN
COMMIT/ROLLBACK inside reusable service procedures.
- ❌ FORBIDDEN
PRAGMA AUTONOMOUS_TRANSACTION to dodge locks or mutating tables.
- ❌ FORBIDDEN to grant permissions on business tables to the application when they can be granted on the API.
- ❌ FORBIDDEN
GRANT ... TO PUBLIC and UTL_HTTP/UTL_FILE/DBMS_JAVA privileges without a use case.
- ❌ FORBIDDEN to deploy leaving
INVALID objects unreviewed.
- ❌ FORBIDDEN to treat the database as the source of truth for the code: the repository wins.
- ❌ FORBIDDEN to create new Forms modules; new functionality is written outside.
- ❌ FORBIDDEN authorisation implemented only in the Forms client.
- ❌ FORBIDDEN to deliver the output of an automatic conversion tool without rewriting it.
- ❌ FORBIDDEN to plan the exit from Forms on a remembered support date: it is read from the
current Lifetime Support Policy PDF, with the exact installed version (§8).
8. Mandatory web verification
Always check, in the official Oracle Lifetime Support Policy PDFs (Fusion Middleware and
Technology Products, which carry an effective date on the cover and are revised several times a
year): Premier/Extended for the exact version of Forms and Reports installed and for that of the
engine; the status of Oracle Reports and of the route to Analytics Publisher; the current version
and support window of APEX and of ORDS; the version and licence of utPLSQL and of any
PL/SQL linter before adopting it; and the quarterly Critical Patch Updates.
Declared gaps (no verified datum, do NOT fill in from memory): (a) the Oracle Forms Statement of
Direction could not be read raw — the two PDF URLs tried returned 404—, so the assertion that
Reports is a terminal release since 12.2.1.3.0 and is still packaged in 14.1.2 comes from release
notes quoted by a search engine, not from a verbatim citation: reconfirm before using it in a
committee; (b) the calendar of version 14.1.2 beyond the Fusion Middleware 14.1.x row of the PDF
is not verified —Oracle publishes error correction dates separately, by Doc ID in My Oracle Support
(authenticated access)—, and error correction ≠ Premier Support: the date that really decides when
you stop receiving patches may be earlier; (c) Forms & Reports licence cost (metrics and contract) —
not verified, do not quote figures; (d) the status and licence of the commercial Forms→APEX conversion
tools — not verified.
Flagged discrepancy: the widespread belief that "Oracle Forms is unsupported" contradicts
Oracle's own document (14.1.x with Premier until Dec 2030); and at the same time, secondary sources
were quoting 14c Premier at Dec 2029 or Dec 2027 before the 2026 revision of the document. The
current PDF wins, with its effective date on the cover — and note that date when quoting it.
If the web contradicts this document, the web wins — flag the discrepancy.
1---2name: plsql-oracle-forms-standards3description: PL/SQL as a program language and Oracle Forms/Reports as a legacy application layer - two things with different futures. Use when writing or reviewing .pks/.pkb/.plb sources, CREATE OR REPLACE PACKAGE / PACKAGE BODY / PROCEDURE / FUNCTION / TRIGGER, anonymous PL/SQL blocks, %TYPE and %ROWTYPE declarations, ref cursors and cursor FOR loops, BULK COLLECT and FORALL with SAVE EXCEPTIONS and LIMIT, EXECUTE IMMEDIATE and DBMS_SQL dynamic SQL, DBMS_ASSERT input validation, bind variables versus literal concatenation, WHEN OTHERS exception handlers, RAISE_APPLICATION_ERROR, PRAGMA AUTONOMOUS_TRANSACTION, AUTHID DEFINER versus CURRENT_USER, package state and ORA-04068, invalid objects and recompilation, DBMS_PROFILER / DBMS_HPROF / PL/Scope, utPLSQL test suites, SQL Developer and SQLcl, and when working with Oracle Forms .fmb/.fmx/.pll/.olb/.mmb modules, Forms Builder, frmcmp and frmweb, WebUtil, Oracle Reports .rdf/.rep, Forms and Reports 12c or 14c, or planning a Forms-to-APEX / Forms-to-Java migration with ORDS.4---56# PL/SQL and Oracle Forms standards78Criteria verified as of **August 2026**. Re-verify on the web before committing to anything (§8).910## 1. Scope and triggers1112Business PL/SQL code (packages, procedures, functions, *triggers*) and Oracle Forms/Reports13applications. Triggers: `.pks`/`.pkb`, `CREATE OR REPLACE PACKAGE`, `BULK COLLECT`, `FORALL`,14`EXECUTE IMMEDIATE`, `DBMS_SQL`, `WHEN OTHERS`, `PRAGMA AUTONOMOUS_TRANSACTION`, `AUTHID`,15utPLSQL, `.fmb`/`.fmx`/`.pll`/`.rdf`, Forms Builder, WebUtil, migration to APEX.1617**The axis: these are two things with different destinies and they are always mentioned together.**18Separate them before deciding anything.1920- **PL/SQL is alive and is the foundation of a great deal of business.** It is not a language in21 retreat: it compiles inside an engine with support well into the 2030s (Oracle Database **19c**:22 Premier until **Dec 2029**, Extended until **Dec 2032**; **26ai**, LTS with GA **Oct 2025**, Premier23 until **Dec 2031** — verified in the PDF *Oracle Technology Products — Oracle Lifetime Support24 Policy*, *Effective Date: May 1, 2026*). Writing new PL/SQL in 2026 can be the right decision.25- **Oracle Forms is what has to be got out of there** — but **not because it is unsupported, which is26 the usual assumption and is false**. Datum verified by reading the PDF *Oracle Fusion Middleware —27 Oracle Lifetime Support Policy*, *Effective Date: April 13, 2026*: **Fusion Middleware 14.1.x** (GA28 **Dec 2024**), whose table footnote explicitly includes *"Forms and Reports"*, has **Premier Support29 until Dec 2030**, **Extended until Dec 2033** and indefinite *Sustaining*. What does press is the30 previous version: **FMW 12c (12.2.x), where the vast majority of the estate lives, has Premier until31 Dec 2026 and Extended until Dec 2027**. And 11gR2 (11.1.2.x) has been out since Dec 2016 (Premier)32 / Dec 2018 (Extended).33- **Oracle Reports is another case**: it is **deprecated since 12.2.1.3.0**, declared a *terminal34 release*, and although it is still packaged with 14.1.2 it receives no new functionality, only35 critical fixes and stack compatibility; the route Oracle points to is **Oracle Analytics Publisher**.3637Operational conclusion: **the Forms clock is not "it is dead", it is "your 12.2.x leaves Premier in38Dec 2026"**. And the real argument for leaving is not the calendar, but that the client platform39—Java applet, then Java Web Start, JRE dependencies on the desktop— ages faster than the product and40that the labour market of people who know how to maintain it has dried up.4142**Not applicable**: `oracle-dba-standards` and `sql-standards` (**already written**) own the **Oracle43engine** (instance, storage, RAC, backup, patching, execution plans, indexes, engine licensing) and44the **SQL language**; here PL/SQL as a **program language** and Forms as an **application layer**,45with `data-platform-standards` and `data-warehouse-modeling-standards` for the analytical side.46`legacy-modernization-standards` is the umbrella of the block —and47`migration-projects-standards` the execution of the cutover: rehearsal, window, coexistence, data48reconciliation, rollback and shutdown of the source— and49`enterprise-architecture-standards` (**already written**) provides the inventory, the TIME model and50the "R"s —here what each option implies technically—, with `refactoring-tech-debt-standards` and51`testing-qa-standards` (**already written**: *strangler fig*, characterisation without tests),52`project-management-standards`, `tech-leadership-standards`, `cicd-standards` and53`git-workflow-standards`. If the destination is Java, `jvm-spring-standards` wins (**already54written**: **modern Java and Spring are theirs and are the usual destination**); for a separate55front-end, `frontend-frameworks-standards` and `api-design-standards`.56`appsec-standards` and `vulnerability-management-standards` (**already written**) provide methodology57and triage; here only the concrete PL/SQL *sinks*. Sister skills of the legacy block —**they share the58"legacy" label and little else**—: `abap-sap-standards`, `mainframe-zos-cobol-standards`,59`ibm-i-rpg-standards`, `jsp-struts-standards`, `coldfusion-standards`, `vb6-standards`.6061## 2. Default decisions6263> Verify on the web before pinning it (§8): FMW and engine support dates, and the APEX version.6465| Decision | By default | Note |66|---|---|---|67| Unit of code | **Package** (`PACKAGE`/`PACKAGE BODY`) | Standalone procedures only for trivial *jobs* |68| SQL in PL/SQL | **Static whenever possible** | It is checked at compile time and records the dependency |69| Unavoidable dynamic | `EXECUTE IMMEDIATE` **with `USING`** + `DBMS_ASSERT` for identifiers | §5; never concatenate values |70| Volume | **`BULK COLLECT` with `LIMIT` + `FORALL`** | The row-by-row loop is performance defect no. 1 |71| Exceptions | Catch **what is expected and named**; rethrow the rest | `WHEN OTHERS THEN NULL`: forbidden (§3) |72| Rights | **`AUTHID DEFINER`** by default, consciously | `CURRENT_USER` when the caller must supply their own permissions |73| Tests | **utPLSQL** (Apache-2.0, verified from the raw file) | §4 |74| New Forms | **None**: no new `.fmb` modules are created | New functionality, outside (§7) |75| Frequent destination | **APEX** if the logic stays in the database; **Java/web** if you have to get out of it | §7 |7677## 3. PL/SQL: conventions that are required7879- **Package as the unit of module**: minimal specification (only what is public), body with the rest.80 The specification is the contract: changing it invalidates all its dependents, the body does not.81- **`%TYPE` and `%ROWTYPE` always** instead of literal types: the code follows the model when the82 column changes size.83- **No repeated loose SQL**: access to a table is concentrated in its package. If the same84 `SELECT` appears in eleven places, eleven places will break with the next model change.85- **Exceptions**: every `EXCEPTION` catches **named** exceptions (`NO_DATA_FOUND`,86 `DUP_VAL_ON_INDEX`, or your own with `EXCEPTION_INIT`) and does something real with them. To87 propagate with context: `RAISE_APPLICATION_ERROR(-20xxx, ...)` preserving88 `SQLERRM`/`DBMS_UTILITY.FORMAT_ERROR_BACKTRACE`, or a bare `RAISE`.89 **`WHEN OTHERS THEN NULL` is the most damaging antipattern in this ecosystem**, and it deserves an90 explanation because it gets written out of habit: it turns a failure into a silent success. The91 transaction carries on, the nightly process "finishes fine", the record was not inserted, nobody92 finds out until the accounts are reconciled months later, and by then there is neither a trace nor93 any way to know how many rows were missing. It is not bad style: it is **undetectable data loss**.94 Its cousin, the `WHEN OTHERS` that does `DBMS_OUTPUT.PUT_LINE` and does not rethrow, is the same95 thing with more steps. Rule: **a `WHEN OTHERS` that does not end in `RAISE` (or in96 `RAISE_APPLICATION_ERROR`) is a review error**; the only legitimate use is to log and rethrow.97- **Transactions**: `COMMIT`/`ROLLBACK` are decided by **whoever starts the unit of work**, not by the98 service procedure that is called from three places; a `COMMIT` buried in a utility function breaks99 the atomicity of all its callers.100- **`PRAGMA AUTONOMOUS_TRANSACTION` only for what must survive the `ROLLBACK`**: auditing and error101 logging. Using it "so the lock stops bothering me" or to avoid a *trigger*'s mutation **hides the102 error and produces inconsistent data**, aggravated by the fact that the autonomous transaction does103 not see the uncommitted changes of the main one and can deadlock against itself.104- **Triggers: the fewest possible.** Business logic spread across *triggers* is impossible to read and105 to debug: the execution order is not evident and the effect fires where nobody expects it. For106 auditing, `FLASHBACK`/audit columns or a compound *trigger* is better than a cascade.107- **Dependencies and invalidation**: every object records what it depends on; a DDL invalidates its108 dependents and recompilation arrives on the first execution (or fails). Operational consequences:109 deploy specification changes **with the application stopped or with the package free of active110 sessions**, because changing a package with state in use causes **ORA-04068** in live sessions;111 review `USER_OBJECTS` looking for `INVALID` **after every deployment** and treat it as a failure,112 not as noise; and avoid package-level state (global variables) unless there is a justified need.113- **Performance**: SQL↔PL/SQL context switches in a loop are the dominant cost. `BULK COLLECT`114 **always with `LIMIT`** (an unbounded collection loads the whole table into session memory),115 `FORALL` with `SAVE EXCEPTIONS` and review of `SQL%BULK_EXCEPTIONS`. Measure with `DBMS_HPROF` and116 `PL/Scope`, not by intuition; the execution plan of the queries belongs to `oracle-dba-standards`.117118**Business logic in the database, as an architecture decision.** In favour, and it is serious: it is119where the data is (zero network latency per operation, sets processed in the engine), integrity is120guaranteed even with several applications and an ETL writing, the transaction is natural, and the code121survives three generations of front-end framework —there is PL/SQL from the nineties serving today—.122Against, and this is serious too: it ties you to the engine (and to its bill), modern engineering123*tooling* arrives late or badly (tests, dependencies, CI, review), it scales only vertically with the124database server, it mixes the application deployment with that of the data, and the labour market gets125narrower every year. **Criteria**: integrity rules and bulk data processes, inside; orchestration,126third-party integration, presentation and logic that changes at the pace of the business, outside. And127the decision is taken **once and written down** (ADR), because the expensive failure is logic128duplicated in both places, diverging in silence.129130## 4. Quality and testing131132- **utPLSQL** as the test framework: verified by reading the raw file, **Apache-2.0** licence133 (`LICENSE` present in both `main` and `develop`). Annotations (`--%suite`, `--%test`), output in134 formats consumable by CI (JUnit/SonarQube), and **per-block coverage**. It is the tool that makes it135 possible to treat PL/SQL as real code.136- **Test what hurts**: monetary calculations, business rules, date cut-offs, and **the error paths**137 —what happens when the row does not exist, when the `FORALL` fails halfway, when two sessions touch138 the same thing—. Test data created and destroyed by the suite itself; a suite that depends on the139 content of the development schema is a status report, not a test.140- **Static analysis**: compiler warnings **enabled and treated as errors** (`PLSQL_WARNINGS`), which141 detect dead code, `WHEN OTHERS` without `RAISE` and misused parameters; `PL/Scope` for dependencies142 and identifiers. There are commercial and free linters for PL/SQL: verify status and licence before143 adopting one (§8).144- **CI**: the schema is built from the repository (**the source wins, not the database**), it is145 deployed to an ephemeral schema, the suite is run and **the build fails if any object is left146 `INVALID`**. That last gate is cheap and catches half the deployment incidents.147148## 5. Security149150- **Bind variables: a security requirement *and* a performance one at the same time.** Concatenating151 values into a statement is SQL injection and additionally generates a different statement per value,152 which fills the *shared pool* and causes a *hard parse* on every execution. With `USING` neither153 happens. There is no case in which concatenating a value is the right option.154- **What does not accept a *bind*** (table, column, schema names; dynamic `ORDER BY` clauses) is155 validated with **`DBMS_ASSERT`** (`SIMPLE_SQL_NAME`, `SQL_OBJECT_NAME`, `SCHEMA_NAME`) **and**156 against a closed allowlist. `DBMS_ASSERT` on its own does not authorise: it checks shape, not157 permission.158- **`AUTHID` is a security decision, not a detail**: `DEFINER` (the default) executes with the owner's159 privileges, so **a `DEFINER` procedure with injectable dynamic SQL hands the owner's privileges to160 the attacker** — it is the classic escalation pattern in Oracle. `CURRENT_USER` (*invoker rights*)161 forces the caller to have their own permissions and limits the damage, in exchange for demanding a162 properly built privilege model. Rule: **public API in `DEFINER`, minimal and with no dynamic SQL;163 anything with dynamic SQL, reviewed line by line.**164- **Exposed database surface**: permissions are granted **on packages, not on tables** — the165 application should not have direct `SELECT`/`INSERT` on the business tables—. Specific roles, no166 `GRANT ... TO PUBLIC`, review of dangerous packages (`UTL_FILE`, `UTL_HTTP`, `UTL_SMTP`,167 `DBMS_SCHEDULER`, `DBMS_JAVA`) and of the network ACLs: **an accessible `UTL_HTTP` turns the168 database into an internal HTTP client, that is, SSRF from the heart of the system**.169- **If there is ORDS/APEX**, the database starts serving HTTP: it is a **new trust boundary**. The170 parsing schema with the fewest privileges, the REST *endpoints* authenticated and authorised one by171 one, and the APEX administration console **never reachable from the internet**.172- **Forms**: credentials on the `frmweb` command line/in the `.fmb`, `EXEC_SQL` with literals, and173 authorisation logic implemented **only in the client** (hiding a button is not a control) are the174 three usual findings. Authorisation is checked **in the database**, always.175- **Patching**: Oracle's quarterly *Critical Patch Updates* cover the database and Fusion Middleware;176 a Forms 12.2.x with no CPU applied is a legacy Java server with no patches. Calendar and triage, in177 `vulnerability-management-standards`.178179## 6. Exit from Forms and Reports180181Four real routes, with their criteria:1821831. **Oracle APEX** — the most frequent destination when the logic already lives in PL/SQL.184 **Verified**: it is a **no-additional-cost feature of the Oracle database** (all editions,185 including Free and Autonomous), **not a separate product that is licensed**; it requires **ORDS**;186 version **26.1**, released on **14 May 2026** (there was no 25.x), with release support until187 **30 Nov 2027** and ~18 months per version. **The key dependency, stated plainly: APEX only runs on188 an Oracle engine.** Migrating to APEX **does not get you off the Oracle bill** —it ties you in189 further—, and that is exactly why Oracle pushes it as the exit from Forms. It is a great option if190 you are going to stay with Oracle for ten years; it is the worst one if the goal was to leave it.1912. **Rewrite to Java/web** (or to whatever stack the house maintains): the only route that cuts the192 dependency. Expensive, and the quality of the result is governed by `jvm-spring-standards`, not by193 this one.1943. **Modernise Forms *in situ*** (move up to 14c, remove the applet, integrate it behind a *reverse195 proxy*): it buys 5-7 years of calendar, it does not solve the staffing or the architecture problem.196 It is a legitimate "invest the minimum" decision for a stable system with a known retirement date.1974. **Automatic conversion tools** (Forms→APEX, Forms→Java): they are useful for the inventory and for198 the scaffolding —screens, LOVs, tables—, and **they produce generated code that has to be redone**:199 they translate the structure of a fat client, with its event model and its per-block state, into a200 stateless environment, and what comes out is neither idiomatic nor maintainable. Use them as an201 accelerator for the mechanical 60%, **never as the final delivery**, and budget the rewrite of the202 rest.203204**And before all four: PL/SQL stays.** The expensive mistake in these migrations is rewriting the205business logic that is already in packages and works too. Forms (presentation and navigation) is206separated from PL/SQL (business), the latter is kept and the former is replaced.207208## 7. Prohibitions209210- ❌ FORBIDDEN `WHEN OTHERS THEN NULL`, and any `WHEN OTHERS` that does not rethrow.211- ❌ FORBIDDEN to concatenate values in dynamic SQL. No exception for "it is a number".212- ❌ FORBIDDEN dynamic SQL with identifiers without `DBMS_ASSERT` **and** an allowlist.213- ❌ FORBIDDEN to process row by row what a set statement or a `FORALL` resolves.214- ❌ FORBIDDEN `BULK COLLECT` without `LIMIT` over tables of unbounded size.215- ❌ FORBIDDEN `COMMIT`/`ROLLBACK` inside reusable service procedures.216- ❌ FORBIDDEN `PRAGMA AUTONOMOUS_TRANSACTION` to dodge locks or *mutating tables*.217- ❌ FORBIDDEN to grant permissions on business tables to the application when they can be granted on the API.218- ❌ FORBIDDEN `GRANT ... TO PUBLIC` and `UTL_HTTP`/`UTL_FILE`/`DBMS_JAVA` privileges without a use case.219- ❌ FORBIDDEN to deploy leaving `INVALID` objects unreviewed.220- ❌ FORBIDDEN to treat the database as the source of truth for the code: the repository wins.221- ❌ FORBIDDEN to create **new** Forms modules; new functionality is written outside.222- ❌ FORBIDDEN authorisation implemented only in the Forms client.223- ❌ FORBIDDEN to deliver the output of an automatic conversion tool without rewriting it.224- ❌ FORBIDDEN to plan the exit from Forms on a **remembered** support date: it is read from the225 current *Lifetime Support Policy* PDF, with the exact installed version (§8).226227## 8. Mandatory web verification228229Always check, in the official **Oracle Lifetime Support Policy** PDFs (*Fusion Middleware* and230*Technology Products*, which carry an effective date on the cover and **are revised several times a231year**): Premier/Extended for the **exact version** of Forms and Reports installed and for that of the232engine; the status of **Oracle Reports** and of the route to Analytics Publisher; the current version233and support window of **APEX** and of **ORDS**; the version and licence of **utPLSQL** and of any234PL/SQL linter before adopting it; and the quarterly **Critical Patch Updates**.235236**Declared gaps (no verified datum, do NOT fill in from memory)**: (a) the **Oracle Forms Statement of237Direction** could not be read raw — the two PDF URLs tried returned **404**—, so the assertion that238Reports is a *terminal release* since 12.2.1.3.0 and is still packaged in 14.1.2 comes from **release239notes quoted by a search engine, not from a verbatim citation**: reconfirm before using it in a240committee; (b) **the calendar of version 14.1.2 beyond the `Fusion Middleware 14.1.x` row** of the PDF241is not verified —Oracle publishes error correction dates separately, by Doc ID in My Oracle Support242(authenticated access)—, and **error correction ≠ Premier Support**: the date that really decides when243you stop receiving patches may be earlier; (c) Forms & Reports licence cost (metrics and contract) —244not verified, do not quote figures; (d) the status and licence of the commercial Forms→APEX conversion245tools — not verified.246247**Flagged discrepancy**: the widespread belief that "Oracle Forms is unsupported" **contradicts248Oracle's own document** (14.1.x with Premier until Dec 2030); and at the same time, secondary sources249were quoting 14c Premier at Dec 2029 or Dec 2027 before the 2026 revision of the document. **The250current PDF wins, with its effective date on the cover** — and note that date when quoting it.251252If the web contradicts this document, **the web wins** — flag the discrepancy.