Skip to content
Homeβ€Ί Databaseβ€Ί Oracle 19c vs 21c vs 23ai: Changes in Error Handling & PL/SQL

Oracle 19c vs 21c vs 23ai: Changes in Error Handling & PL/SQL

Where developers are forged. Β· Structured learning Β· Free forever.
πŸ“ Part of: PL/SQL β†’ Topic 26 of 27
What changed in recent Oracle versions regarding exceptions and ORA-06512 behavior.
πŸ”₯ Advanced β€” solid Database foundation required
In this tutorial, you'll learn
What changed in recent Oracle versions regarding exceptions and ORA-06512 behavior.
  • ORA-06512 message format is stable across 19c, 21c, and 23ai β€” but the errors that trigger it, the stack depth, and the surrounding diagnostics have all changed
  • 23ai enforces stricter implicit conversion: trailing whitespace in TO_NUMBER now raises ORA-06502 β€” fix with TRIM + explicit format masks on 19c before upgrading
  • JSON error reporting in 23ai includes path expression, character position, and expected-vs-actual type β€” update SQLERRM parsing patterns in monitoring tools
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
⚑Quick Answer
  • Oracle 23ai introduced SQL domains, native BOOLEAN in SQL, stricter implicit conversion, and enhanced JSON error handling β€” all of which change error paths your code has relied on since 19c
  • Oracle 21c added immutable tables (ORA-43923), SQL macros (line number shifts in backtraces), and JSON duality views with new error diagnostics
  • Oracle 19c remains the long-term support baseline with permissive implicit conversion β€” trailing whitespace in TO_NUMBER calls succeeds silently, masking data quality issues
  • Performance insight: 23ai SQL Firewall adds 5–15 ms overhead per statement in capture mode but catches injection-pattern errors before they reach PL/SQL exception handlers
  • Production insight: code tested and stable on 19c may raise ORA-06502 on 23ai due to stricter type coercion β€” the ORA-06512 stack looks identical, but the triggering error is new
  • Biggest migration mistake: assuming ORA-06512 behavior is identical across versions β€” the message format is stable, but the errors that produce it, the stack depth, and the surrounding diagnostics have all shifted
🚨 START HERE
Version Upgrade Error Audit Cheat Sheet
Commands to identify error handling code affected by version changes. Run these on your source version to build a pre-upgrade audit, then run again on the target version to compare behavior.
🟑Need to find all implicit conversion code before upgrading to 23ai
Immediate ActionSearch source code for TO_NUMBER and TO_DATE calls without format masks β€” these are the highest-risk code paths for 23ai behavioral changes
Commands
SELECT owner, name, type, line, TRIM(text) AS text FROM dba_source WHERE REGEXP_LIKE(text, 'TO_NUMBER\s*\([^,)]*\)', 'i') AND owner NOT IN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS') ORDER BY owner, name, line;
SELECT owner, name, type, line, TRIM(text) AS text FROM dba_source WHERE REGEXP_LIKE(text, 'TO_DATE\s*\([^,)]*\)', 'i') AND owner NOT IN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS') ORDER BY owner, name, line;
Fix NowAdd explicit format masks and TRIM to every identified conversion. TO_NUMBER(TRIM(v_input), '999999999999.99') is safe across all versions. TO_DATE(TRIM(v_input), 'YYYY-MM-DD HH24:MI:SS') eliminates NLS-dependent behavior.
🟑Need to compare error behavior between source and target Oracle versions
Immediate ActionCheck for invalid objects and compilation errors on the target version β€” these indicate code that will fail before reaching runtime error handling
Commands
SELECT object_name, object_type, status, last_ddl_time FROM dba_objects WHERE status = 'INVALID' AND owner NOT IN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS') ORDER BY object_type, object_name;
SELECT owner, name, type, line, position, text, attribute FROM dba_errors WHERE owner NOT IN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS') ORDER BY owner, name, sequence;
Fix NowRecompile all invalid objects with EXEC DBMS_UTILITY.COMPILE_SCHEMA('YOUR_SCHEMA', FALSE); β€” then fix compilation errors before running any behavioral tests. Compilation errors on the target version often indicate syntax that the newer compiler handles differently.
🟑Need to audit JSON error handling code for 23ai upgrade
Immediate ActionFind all JSON function calls and check which ones lack explicit error clauses β€” these will behave differently on 23ai
Commands
SELECT owner, name, type, line, TRIM(text) AS text FROM dba_source WHERE REGEXP_LIKE(text, 'JSON_(TABLE|VALUE|QUERY|EXISTS|SERIALIZE|MERGEPATCH)\s*\(', 'i') AND owner NOT IN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS') ORDER BY owner, name, line;
SELECT owner, name, type, line, TRIM(text) AS text FROM dba_source WHERE UPPER(text) LIKE '%ERROR ON ERROR%' OR UPPER(text) LIKE '%NULL ON ERROR%' OR UPPER(text) LIKE '%DEFAULT%ON ERROR%' ORDER BY owner, name, line;
Fix NowCross-reference the two result sets. Any JSON function call from the first query that does not have a corresponding error clause from the second query needs an explicit ERROR ON ERROR or NULL ON ERROR clause added. This makes behavior deterministic across all Oracle versions.
🟑Need to identify exception handlers that may behave differently on 23ai
Immediate ActionFind all WHEN OTHERS handlers and check for generic catch-all patterns that may mask version-specific errors
Commands
SELECT owner, name, type, line, TRIM(text) AS text FROM dba_source WHERE UPPER(text) LIKE '%WHEN OTHERS%' AND owner NOT IN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS') ORDER BY owner, name, line;
SELECT owner, name, type, line, TRIM(text) AS text FROM dba_source WHERE UPPER(text) LIKE '%PRAGMA EXCEPTION_INIT%' AND owner NOT IN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS') ORDER BY owner, name, line;
Fix NowReview every WHEN OTHERS handler β€” it should either re-raise the exception or log SQLCODE and SQLERRM before continuing. Generic WHEN OTHERS THEN NULL patterns will silently swallow new 23ai errors and make debugging impossible.
Production Incident19c-to-23ai Upgrade Breaks Batch Error HandlingA payment processing batch that ran clean on 19c for three years produced 47 new ORA-06512 errors on its first night after a 23ai upgrade β€” every one of them a conversion failure that 19c had silently tolerated.
SymptomBatch job produced 47 ORA-06512 stacks on first 23ai production run. Previous 19c runs over the prior 14 months had zero errors in the same code paths. All 47 errors were ORA-06502 (PL/SQL: numeric or value error) occurring inside currency conversion procedures. The ORA-06512 lines pointed to TO_NUMBER calls that had been stable for years. Alert pages fired at 02:17 AM and the on-call DBA initially suspected data corruption because the code had not been modified.
AssumptionThe team assumed ORA-06512 behavior was identical across versions. They had run the standard Oracle pre-upgrade utility (preupgrade.jar), verified no invalid objects, recompiled all PL/SQL, and confirmed that test transactions succeeded. They did not test error handling paths or edge-case conversion scenarios because those paths had been error-free on 19c for the entire lifetime of the application.
Root causeOracle 23ai enforces stricter implicit data type conversion rules than 19c. The batch ingested payment amounts from a flat file parser that produced VARCHAR2 values with trailing whitespace β€” values like '1249.99 ' and '500.00 '. On 19c, TO_NUMBER silently trimmed the whitespace and returned the numeric value. On 23ai, the same TO_NUMBER call raises ORA-06502 because 23ai no longer performs implicit whitespace trimming during numeric conversion. The conversion procedures had no explicit TRIM calls and no format masks β€” they relied entirely on Oracle's permissive 19c behavior. The ORA-06512 stack structure was byte-for-byte identical to what 19c would have produced if the error had occurred; the difference was that 19c never raised the error in the first place.
Fix1. Queried DBA_SOURCE to identify every TO_NUMBER and TO_DATE call without an explicit format mask across all application schemas β€” found 213 instances across 47 packages 2. Added explicit TRIM(BOTH FROM ...) wrapping on all VARCHAR2 inputs to numeric conversion functions 3. Added TO_NUMBER format masks ('999999999999.99') to all currency conversion calls for deterministic behavior 4. Added WHEN VALUE_ERROR exception handlers with input value logging so the batch could continue processing and report failures at the end rather than aborting on the first bad record 5. Created a regression test suite with 340 edge-case conversion inputs including trailing spaces, leading spaces, mixed whitespace, empty strings, and NLS-specific decimal separators 6. Ran the full regression suite against 23ai in a staging environment and verified zero unexpected errors before re-enabling the production batch 7. Added the regression suite to the CI pipeline so future patch upgrades are tested automatically
Key Lesson
Always retest error handling paths β€” not just happy paths β€” after major version upgradesOracle 23ai enforces stricter implicit conversion rules than 19c β€” this is the single most common source of post-upgrade errorsORA-06512 stack structure is identical across versions, which makes the change deceptive β€” the stack looks the same, but errors that never fired on 19c now fire on 23aiThe Oracle pre-upgrade utility does not test application-level PL/SQL behavior β€” it checks database-level compatibility onlyImplicit conversion without TRIM and format masks is technical debt that 19c hid from you
Production Debug GuideSymptom-to-action mapping for post-upgrade error handling issues across 19c, 21c, and 23ai
New ORA-06502 (numeric or value error) after 19c-to-23ai upgrade in code that was previously error-free→This is the most common post-upgrade issue. Search for implicit VARCHAR2-to-NUMBER and VARCHAR2-to-DATE conversions that lack explicit TRIM and format masks. Run: SELECT owner, name, type, line, text FROM dba_source WHERE REGEXP_LIKE(text, 'TO_NUMBER\s*\(', 'i') AND NOT REGEXP_LIKE(text, 'TRIM', 'i') AND owner NOT IN ('SYS','SYSTEM','ORACLE_MAINTAINED') ORDER BY owner, name, line; — this finds TO_NUMBER calls without a nearby TRIM. Add TRIM(BOTH FROM input) and explicit format masks to every identified call. Test with trailing whitespace, leading whitespace, and empty string inputs.
ORA-06512 stack depth changed after upgrade — stack traces have fewer or more frames than the same error on the previous version→Oracle's PL/SQL compiler in 21c and 23ai applies more aggressive inlining and dead-code elimination. Intermediate call frames may be optimized away, making FORMAT_ERROR_BACKTRACE return fewer ORA-06512 lines. If your monitoring or logging code parses stack traces and counts frames to identify error location, it will break. Verify by checking compiled object dependencies: SELECT * FROM dba_dependencies WHERE referenced_name = 'SUSPECT_OBJECT' AND owner NOT IN ('SYS','SYSTEM'); — then recompile with PLSQL_OPTIMIZE_LEVEL = 1 to disable aggressive optimization and compare stack depths. For production, update your parsing logic to match on object name rather than frame count.
JSON-related errors (ORA-40441, ORA-40462, ORA-40834) appearing on 23ai that were never seen on 19c→Oracle 23ai has significantly stricter JSON parsing and enhanced error diagnostics. JSON_VALUE, JSON_TABLE, and JSON_QUERY calls that silently returned NULL on 19c for malformed input may now raise errors on 23ai, especially when RETURNING clauses specify a target data type. Audit all JSON function calls: SELECT owner, name, type, line, text FROM dba_source WHERE REGEXP_LIKE(text, 'JSON_(TABLE|VALUE|QUERY|EXISTS|SERIALIZE|TRANSFORM)\s*\(', 'i') AND owner NOT IN ('SYS','SYSTEM') ORDER BY owner, name, line; — then verify each call has an explicit error clause (ERROR ON ERROR, NULL ON ERROR, or DEFAULT ... ON ERROR). Add explicit error clauses to every JSON function call to make behavior deterministic across versions.
Boolean handling differences in PL/SQL exception blocks on 23ai — conditions that evaluated one way on 19c now evaluate differently→Oracle 23ai supports native BOOLEAN in SQL (not just PL/SQL). This means expressions like WHERE my_boolean_function(id) = TRUE now work in SQL, but NULL handling in boolean contexts may differ from the 19c PL/SQL-only behavior. If your exception handlers use boolean expressions to decide which error path to take, test NULL propagation explicitly. Check for patterns like IF v_bool THEN ... where v_bool could be NULL — on 23ai this may behave differently in SQL contexts versus PL/SQL contexts. Add explicit IS NOT NULL checks before boolean evaluations in exception handlers.
SQL Firewall (23ai) blocking or logging legitimate error-raising procedures as security anomalies→Oracle 23ai SQL Firewall monitors SQL statements against a captured baseline of normal activity. Procedures that use RAISE_APPLICATION_ERROR for business logic flow control, or that intentionally generate exceptions as part of validation, may be flagged as anomalous if they were not captured during the baseline period. Check SQL Firewall violation logs: SELECT * FROM DBA_SQL_FIREWALL_VIOLATIONS WHERE username = 'APP_USER' ORDER BY firewall_time DESC; — then whitelist known error-raising procedures. Always run SQL Firewall in observation mode for at least one full business cycle before switching to blocking mode.
ORA-43923 (immutable table modification) errors appearing after 21c or 23ai upgrade→If your upgrade introduced immutable tables or if existing tables were converted to immutable, any UPDATE or DELETE operations will raise ORA-43923. This is a new error code not present in 19c. Search for DML against immutable tables: SELECT table_name, immutable FROM dba_tables WHERE immutable = 'YES' AND owner = 'YOUR_SCHEMA'; — then audit all application DML to ensure it does not attempt UPDATE or DELETE against these tables. Add PRAGMA EXCEPTION_INIT declarations for ORA-43923 in your error handling packages.
DDL scripts failing with ORA-00955 (name already used) on 19c but succeeding on 23ai, or vice versa→Oracle 23ai supports IF NOT EXISTS syntax for CREATE TABLE, CREATE INDEX, and other DDL statements. If your deployment scripts were written for 23ai and use IF NOT EXISTS, they will fail on 19c with ORA-02304 or similar syntax errors. If scripts were written for 19c with manual existence checks (SELECT COUNT(*) before CREATE), they will still work on 23ai but are unnecessarily verbose. Standardize deployment scripts to use the minimum target version syntax or use conditional compilation.

Oracle 19c, 21c, and 23ai represent three generations of the database engine, and each one shifted the error handling landscape in ways that matter for production PL/SQL code. ORA-06512 remains the universal stack trace indicator β€” it tells you the object name and line number where an unhandled exception propagated β€” and its message format has not changed across any of these versions. What has changed is the ecosystem surrounding it.

Oracle 19c is the stable, long-term-support baseline that most production systems still run. Its implicit conversion rules are permissive, its error diagnostics are well-understood, and its PL/SQL compiler behavior is predictable. Oracle 21c, the innovation release, introduced immutable tables, SQL macros, and enhanced JSON support β€” each of which created new error conditions or shifted where errors appear in stack traces. Oracle 23ai represents the most significant overhaul: stricter implicit type coercion, native BOOLEAN in SQL, SQL domains with new constraint error codes, IF NOT EXISTS DDL syntax that eliminates entire error categories, and SQL Firewall as a new security layer that can intercept exception-raising statements.

The core challenge for any team planning a version upgrade is this: error handling code written against 19c assumptions will produce different results on 23ai. Not because ORA-06512 changed, but because the errors feeding into it changed, the compiler optimizations that determine stack depth changed, and the diagnostic detail Oracle provides alongside the stack changed. This article walks through each version's specific changes, shows you exactly what breaks and why, and gives you a systematic testing strategy to catch behavioral differences before they reach production.

Oracle 19c: The Stable Baseline

Oracle 19c is the long-term support release and the version most production systems still run as of early 2026. Its error handling behavior is the reference point against which all upgrade changes must be measured. Understanding exactly how 19c handles errors is prerequisite to understanding what 21c and 23ai changed.

ORA-06512 in 19c follows a consistent format: ORA-06512: at "OWNER.OBJECT_NAME", line N. Every unhandled exception that propagates through a PL/SQL call stack produces one ORA-06512 line per stack frame, creating a complete backtrace from the point of failure to the top-level caller. The FORMAT_ERROR_BACKTRACE function (available since 10gR2 but stable and reliable in 19c) returns the same information as a CLOB, including line numbers for every frame in the call stack.

The critical 19c behavior to understand for upgrade planning is its permissive implicit data type conversion. TO_NUMBER called on a VARCHAR2 value with trailing whitespace β€” '123.45 ' β€” succeeds silently on 19c. The database trims the whitespace and returns 123.45 without raising an error. This is convenient but it masks data quality issues: if your flat file parser or API integration produces padded strings, 19c will never tell you. Your code has been relying on this permissiveness, possibly for years, without anyone realizing it.

PRAGMA EXCEPTION_INIT behavior in 19c is stable and well-tested. You can bind any Oracle error number to a named exception and handle it specifically. DBMS_UTILITY.FORMAT_ERROR_STACK returns the error message without the backtrace; FORMAT_ERROR_BACKTRACE returns the backtrace without the error message. Both functions return consistent results regardless of call depth or compilation optimization level in 19c.

19c also introduced polymorphic table functions and qualified expressions for collections, both of which reduced certain categories of errors. Qualified expressions in particular reduced ORA-06531 (Reference to uninitialized collection) errors because they made it easier to initialize collections inline. This is a positive change but means that code ported from pre-19c might have ORA-06531 handlers that are now dead code.

oracle_19c_error_baseline.sql Β· SQL
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
-- Oracle 19c: implicit conversion succeeds silently
-- This is the behavior that breaks on 23ai
DECLARE
  v_input  VARCHAR2(20) := '123.45  ';  -- trailing spaces from file parser
  v_number NUMBER;
BEGIN
  v_number := TO_NUMBER(v_input);  -- succeeds on 19c, FAILS on 23ai
  DBMS_OUTPUT.PUT_LINE('Converted: ' || v_number);
EXCEPTION
  WHEN VALUE_ERROR THEN
    DBMS_OUTPUT.PUT_LINE('Value error: ' || SQLERRM);
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('Unexpected: ' || SQLERRM);
END;
/
-- Output on 19c: Converted: 123.45
-- Output on 23ai: Value error: ORA-06502: PL/SQL: numeric or value error


-- Oracle 19c: ORA-06512 stack trace format (unchanged across all versions)
-- ORA-01403: no data found
-- ORA-06512: at "PAYMENTS.CURRENCY_CONVERT", line 42
-- ORA-06512: at "PAYMENTS.BATCH_PROCESSOR", line 187
-- ORA-06512: at line 1


-- Oracle 19c: FORMAT_ERROR_BACKTRACE behavior
CREATE OR REPLACE PROCEDURE inner_proc_19c IS
BEGIN
  RAISE NO_DATA_FOUND;
END inner_proc_19c;
/

CREATE OR REPLACE PROCEDURE middle_proc_19c IS
BEGIN
  inner_proc_19c;
END middle_proc_19c;
/

CREATE OR REPLACE PROCEDURE outer_proc_19c IS
BEGIN
  middle_proc_19c;
EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('--- BACKTRACE ---');
    DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    DBMS_OUTPUT.PUT_LINE('--- ERROR STACK ---');
    DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_STACK);
END outer_proc_19c;
/

BEGIN outer_proc_19c; END;
/
-- Output on 19c (consistent line numbers, full depth):
-- --- BACKTRACE ---
-- ORA-06512: at "MYSCHEMA.INNER_PROC_19C", line 3
-- ORA-06512: at "MYSCHEMA.MIDDLE_PROC_19C", line 3
-- ORA-06512: at "MYSCHEMA.OUTER_PROC_19C", line 3
-- --- ERROR STACK ---
-- ORA-01403: no data found


-- 19c: Qualified expression reduces ORA-06531 risk
DECLARE
  TYPE t_amounts IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
  -- Pre-19c you had to declare then populate separately,
  -- risking ORA-06531 if you forgot to initialize
  v_amounts t_amounts := t_amounts(1 => 100.50, 2 => 200.75, 3 => 350.00);
BEGIN
  FOR i IN 1..v_amounts.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE('Amount ' || i || ': ' || v_amounts(i));
  END LOOP;
END;
/
Mental Model
19c as Your Regression Baseline
Think of 19c behavior as the contract your code was written against. Any deviation in a later version is a behavioral change that demands a code review β€” even if Oracle considers it a correctness improvement.
  • 19c implicit conversion is permissive β€” trailing spaces, leading spaces, and mixed whitespace in numeric strings are silently tolerated
  • 19c FORMAT_ERROR_BACKTRACE always includes line numbers for every frame in the call stack without optimizer-driven frame elimination
  • 19c PRAGMA EXCEPTION_INIT binding is stable β€” no known behavioral differences from 12c through 19c
  • 19c extended support runs through April 2027 β€” most production systems are still on it, making it the de facto standard for error handling assumptions
  • 19c permissiveness is a double-edged sword: your code works, but it hides data quality problems that 23ai will expose
πŸ“Š Production Insight
19c implicit conversion masks data quality issues that have been accumulating for years. Code that has run error-free on 19c for the entire lifetime of an application may fail immediately on 23ai β€” not because of a bug in 23ai, but because 19c was silently compensating for upstream data problems. Audit every implicit conversion code path before any version upgrade. The most efficient approach is to add TRIM and explicit format masks on 19c first, verify the code still works, and then upgrade. This decouples the conversion fix from the version change and reduces the blast radius if something goes wrong.
🎯 Key Takeaway
19c is the stable baseline for error handling behavior and the version most production code was written against. Implicit conversion is permissive β€” it masks data quality issues that will surface on 23ai. Document 19c behavior and fix implicit conversions before upgrading to prevent regressions that look like 23ai bugs but are actually long-hidden data problems.
19c Upgrade Planning Decision Matrix
IfStaying on 19c through end of extended support (April 2027)
β†’
UseNo immediate code changes required β€” but begin documenting all implicit conversion patterns and error handling behaviors now as your regression test baseline. Budget for increasing extended support costs.
IfPlanning upgrade to 23ai within 12 months
β†’
UseBegin auditing implicit conversion code, JSON error handling, and WHEN OTHERS handlers immediately. Fix implicit conversions on 19c first (add TRIM and format masks) so you can validate the fix before introducing version-change variables.
IfRunning 19c with no upgrade planned and no budget for one
β†’
UseDocument all known error handling behaviors as regression test cases. When the upgrade eventually happens β€” and it will β€” you will need this baseline. Also fix the implicit conversion code now: it is correct on any version and eliminates a class of latent bugs.

Oracle 21c: Incremental but Consequential Changes

Oracle 21c is an innovation release β€” it does not have long-term support, and most production environments skip it in favor of the 19c-to-23ai upgrade path. However, understanding 21c changes matters because many of its features carried forward into 23ai, and some of the error handling differences teams encounter on 23ai actually originated in 21c.

The most impactful 21c change for error handling is immutable tables. An immutable table rejects any UPDATE or DELETE operation with ORA-43923 (DML operation not allowed on table). This error code did not exist in 19c, which means any PRAGMA EXCEPTION_INIT declarations, any error logging logic that filters by SQLCODE, and any monitoring alerts that match on error patterns will miss it unless you add explicit support. The ORA-06512 stack for ORA-43923 looks exactly like any other unhandled DML error β€” the only difference is the root cause error code.

SQL macros, introduced in 21c, change query compilation in a way that affects ORA-06512 line numbers. A SQL macro expands inline at compile time, replacing the macro call with the expanded SQL text. If an error occurs inside the expanded SQL, the line number reported in FORMAT_ERROR_BACKTRACE refers to the expanded code, not the macro definition. This means the line number in your ORA-06512 stack may not correspond to any visible line in your source code. The object name will be the calling procedure, not the macro itself.

Automatic indexing in 21c can change query execution plans without DBA intervention. While this does not directly change error handling, it can cause errors to appear in different code paths. A query that previously used a full table scan and hit a resource limit error now uses an auto-created index and succeeds β€” or vice versa. If your error handling logic is path-dependent (different handlers for different error scenarios), automatic indexing can invalidate your assumptions.

21c also enhanced JSON support with the native JSON data type (as distinct from VARCHAR2 or CLOB containing JSON text). JSON_VALUE and JSON_TABLE with the native JSON type produce slightly different error messages than their VARCHAR2/CLOB counterparts. The error codes are the same, but the diagnostic text includes additional context about the JSON binary representation.

The ORA-06512 format itself remained unchanged from 19c in 21c. Stack trace structure, line numbering, and FORMAT_ERROR_BACKTRACE output are identical for the same code paths.

oracle_21c_error_changes.sql Β· SQL
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
-- Oracle 21c: Immutable table introduces new error code ORA-43923
CREATE IMMUTABLE TABLE audit_immutable_21c (
  event_id   NUMBER GENERATED ALWAYS AS IDENTITY,
  event_type VARCHAR2(50),
  event_data CLOB,
  created_at TIMESTAMP DEFAULT SYSTIMESTAMP
) NO DROP UNTIL 365 DAYS IDLE
  NO DELETE UNTIL 730 DAYS AFTER INSERT;

-- INSERT works normally
INSERT INTO audit_immutable_21c (event_type, event_data)
VALUES ('LOGIN', '{"user": "admin", "ip": "10.0.1.15"}');
COMMIT;

-- UPDATE raises new error not present in 19c
BEGIN
  UPDATE audit_immutable_21c
  SET event_type = 'MODIFIED'
  WHERE event_id = 1;
EXCEPTION
  WHEN OTHERS THEN
    -- SQLCODE = -43923
    -- ORA-43923: Modification of the data in Immutable table is not allowed
    -- ORA-06512: at line 2
    DBMS_OUTPUT.PUT_LINE('SQLCODE: ' || SQLCODE);
    DBMS_OUTPUT.PUT_LINE('SQLERRM: ' || SQLERRM);
    DBMS_OUTPUT.PUT_LINE('Stack:   ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
END;
/

-- Add PRAGMA EXCEPTION_INIT for the new error code
DECLARE
  e_immutable_violation EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_immutable_violation, -43923);
BEGIN
  DELETE FROM audit_immutable_21c WHERE event_id = 1;
EXCEPTION
  WHEN e_immutable_violation THEN
    DBMS_OUTPUT.PUT_LINE('Cannot modify immutable table - expected behavior');
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('Unexpected: ' || SQLERRM);
    RAISE;
END;
/


-- Oracle 21c: SQL Macro affects ORA-06512 line numbers
CREATE OR REPLACE FUNCTION active_orders_macro
  RETURN VARCHAR2 SQL_MACRO(TABLE) IS
BEGIN
  RETURN q'[
    SELECT o.order_id, o.customer_id, o.amount, o.status
    FROM   orders o
    WHERE  o.status = 'ACTIVE'
    AND    o.created_at > SYSDATE - 30
  ]';
END active_orders_macro;
/

-- When this macro is called and an error occurs inside it,
-- the ORA-06512 line number refers to the EXPANDED code
-- in the calling context, not the macro definition
CREATE OR REPLACE PROCEDURE process_active_orders_21c IS
  v_total NUMBER;
BEGIN
  SELECT SUM(amount) INTO v_total
  FROM   active_orders_macro();  -- macro expands here
  -- If the expansion causes an error, ORA-06512 reports
  -- a line number in THIS procedure, not in the macro
  DBMS_OUTPUT.PUT_LINE('Total: ' || v_total);
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    DBMS_OUTPUT.PUT_LINE('No active orders found');
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('Error at expanded macro location');
    DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    RAISE;
END process_active_orders_21c;
/


-- Oracle 21c: Native JSON data type
-- Error messages include additional binary format context
DECLARE
  v_json  JSON := JSON('{"amount": "not_a_number"}');
  v_amount NUMBER;
BEGIN
  v_amount := JSON_VALUE(v_json, '$.amount' RETURNING NUMBER);
  -- Default behavior without error clause: returns NULL
  -- With ERROR ON ERROR: raises ORA-40441 with enhanced diagnostics
  DBMS_OUTPUT.PUT_LINE('Amount: ' || NVL(TO_CHAR(v_amount), 'NULL'));
END;
/
πŸ”₯21c Changes That Carry Forward to 23ai
  • ORA-43923 (immutable table) is new in 21c β€” add PRAGMA EXCEPTION_INIT declarations to your error handling packages before upgrading
  • SQL macros shift ORA-06512 line numbers to the expansion site, not the macro definition β€” this affects any code that parses line numbers for error correlation
  • Automatic indexing changes query plans silently β€” error handling that depends on specific execution paths may see different errors
  • Native JSON data type produces slightly different error diagnostic text than VARCHAR2/CLOB JSON β€” update any error message parsing logic
  • 21c is an innovation release without long-term support β€” most teams skip it, but all these behaviors exist in 23ai
πŸ“Š Production Insight
21c is an innovation release, not long-term support. Most production environments skip 21c and upgrade directly from 19c to 23ai. This is fine from a support perspective, but it means you encounter both 21c and 23ai behavioral changes simultaneously during the upgrade. If your risk tolerance is low, stand up a 21c test environment and test your PL/SQL there first to isolate 21c-specific changes (immutable tables, SQL macros, automatic indexing) from 23ai-specific changes (stricter conversion, SQL Firewall, BOOLEAN in SQL). This two-step validation adds a week to the testing timeline but cuts diagnostic time in half when you encounter unexpected errors.
🎯 Key Takeaway
21c introduced immutable tables (ORA-43923), SQL macros (line number shifts in backtraces), and automatic indexing (silent plan changes) β€” all of which affect error handling paths. ORA-06512 format remained unchanged from 19c. 21c is an innovation release that most teams skip, but its features carry forward into 23ai and must be tested regardless of whether you deploy 21c in production.

Oracle 23ai: The Big Shift in Error Handling Behavior

Oracle 23ai (initially announced as 23c, rebranded for the AI capabilities) represents the most significant change to PL/SQL error handling behavior since the introduction of FORMAT_ERROR_BACKTRACE in 10gR2. The ORA-06512 message format itself has not changed β€” it still reads ORA-06512: at "OWNER.OBJECT", line N β€” but nearly everything around it has shifted.

The headline change is stricter implicit data type conversion. On 19c and 21c, TO_NUMBER('123.45 ') succeeds because Oracle silently trims trailing whitespace. On 23ai, this raises ORA-06502 (PL/SQL: numeric or value error). This is not a bug β€” Oracle considers it a correctness improvement β€” but it breaks code that has been running error-free for years. The fix is straightforward (add TRIM and format masks), but the diagnosis is not, because the ORA-06512 stack looks identical to what 19c would have produced if the error had occurred. The difference is that 19c never raised the error.

JSON error reporting in 23ai is substantially enhanced. When a JSON function fails, the error message now includes the JSON path expression that failed, the character position within the JSON document, and the expected versus actual data type. This is excellent for debugging but changes the format of error messages that your monitoring or logging systems may parse. If you extract error details from SQLERRM using SUBSTR or REGEXP_SUBSTR, your patterns may need updating.

SQL Firewall is a new 23ai security feature that monitors all SQL statements against a captured baseline of normal activity. It can operate in observation mode (log anomalies) or blocking mode (reject anomalous SQL). This affects error handling in two ways. First, SQL Firewall adds 5–15 ms overhead per statement in capture mode, which can push time-sensitive operations past their timeout thresholds and produce timeout-related errors. Second, procedures that use RAISE_APPLICATION_ERROR for business logic flow control β€” a common pattern for validation β€” may be flagged as anomalous if the error-raising code path was not exercised during the baseline capture period.

Native BOOLEAN support in SQL is a significant language change. Prior to 23ai, BOOLEAN was available only in PL/SQL β€” you could not use a boolean expression in a SQL WHERE clause or return BOOLEAN from a SQL function call. On 23ai, you can. This changes error behavior in subtle ways: a function that returns BOOLEAN can now be called in a SQL context where NULL handling follows SQL three-valued logic rather than PL/SQL two-valued logic. Exception handlers that evaluate boolean conditions may behave differently.

SQL domains are new to 23ai and introduce a new category of constraint errors. A domain defines a reusable data type with constraints, display properties, and ordering rules. When a domain constraint is violated, Oracle raises a domain-specific error that is distinct from a standard CHECK constraint violation. This is a new error code that must be added to your PRAGMA EXCEPTION_INIT declarations and monitoring filters.

IF NOT EXISTS syntax for DDL statements eliminates entire categories of errors. CREATE TABLE IF NOT EXISTS, CREATE INDEX IF NOT EXISTS, and DROP TABLE IF EXISTS prevent ORA-00955 (name already used) and ORA-00942 (table or view does not exist) errors. This is a pure improvement β€” it eliminates error handling code you previously had to write β€” but it means deployment scripts written for 23ai will not work on 19c without modification.

oracle_23ai_error_changes.sql Β· SQL
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
-- Oracle 23ai: Stricter implicit conversion β€” THE most common upgrade breaker
DECLARE
  v_input  VARCHAR2(20) := '123.45  ';  -- trailing spaces from file parser
  v_number NUMBER;
BEGIN
  v_number := TO_NUMBER(v_input);  -- FAILS on 23ai with ORA-06502
  DBMS_OUTPUT.PUT_LINE('Result: ' || v_number);
EXCEPTION
  WHEN VALUE_ERROR THEN
    -- This handler fires on 23ai but NEVER fires on 19c for this input
    DBMS_OUTPUT.PUT_LINE('23ai rejected input: [' || v_input || ']');
    DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
    DBMS_OUTPUT.PUT_LINE('Stack: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
END;
/

-- 23ai-safe fix: works on ALL versions (19c, 21c, 23ai)
DECLARE
  v_input  VARCHAR2(20) := '123.45  ';
  v_number NUMBER;
BEGIN
  v_number := TO_NUMBER(TRIM(v_input), '999999999999.99');
  DBMS_OUTPUT.PUT_LINE('Result: ' || v_number);  -- 123.45 on all versions
END;
/


-- Oracle 23ai: Enhanced JSON error reporting with path and position detail
-- Compare error detail between versions
BEGIN
  DECLARE
    v_result NUMBER;
  BEGIN
    SELECT JSON_VALUE(
      '{"order": {"amount": "not_a_number", "currency": "USD"}}',
      '$.order.amount'
      RETURNING NUMBER
      ERROR ON ERROR
    ) INTO v_result FROM dual;
  EXCEPTION
    WHEN OTHERS THEN
      -- 19c: ORA-40441: JSON syntax error
      -- 23ai: ORA-40441: JSON syntax error at position 25
      --       for JSON path '$.order.amount'
      --       expected NUMBER, found STRING
      DBMS_OUTPUT.PUT_LINE('JSON Error: ' || SQLERRM);
      DBMS_OUTPUT.PUT_LINE('Stack: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
  END;
END;
/


-- Oracle 23ai: BOOLEAN in SQL β€” changes error behavior in conditional paths
CREATE OR REPLACE FUNCTION is_premium_customer(
  p_customer_id IN NUMBER
) RETURN BOOLEAN IS
  v_total NUMBER;
BEGIN
  SELECT SUM(amount) INTO v_total
  FROM   orders
  WHERE  customer_id = p_customer_id
  AND    status = 'COMPLETED';
  
  RETURN NVL(v_total, 0) > 10000;
END is_premium_customer;
/

-- 23ai: BOOLEAN function directly in SQL WHERE clause
-- This was a syntax error on 19c and 21c
SELECT customer_id, name
FROM   customers
WHERE  is_premium_customer(customer_id);  -- 23ai only
-- If the function raises an exception, the error path now
-- includes SQL-context boolean evaluation, which produces
-- different FORMAT_ERROR_BACKTRACE output than PL/SQL-context


-- Oracle 23ai: SQL Domains introduce new constraint error codes
CREATE DOMAIN positive_amount AS NUMBER(12,2)
  CONSTRAINT positive_amount_chk CHECK (positive_amount > 0)
  DISPLAY CASE WHEN positive_amount > 1000 THEN TO_CHAR(positive_amount, '999,999.99')
              ELSE TO_CHAR(positive_amount, '999.99') END
  ORDER positive_amount;

CREATE TABLE domain_orders_23ai (
  order_id NUMBER GENERATED ALWAYS AS IDENTITY,
  amount   positive_amount  -- domain-typed column
);

-- Domain violation raises a DIFFERENT error code than CHECK constraint
BEGIN
  INSERT INTO domain_orders_23ai (amount) VALUES (-50.00);
EXCEPTION
  WHEN OTHERS THEN
    -- Domain violation error code differs from standard ORA-02290
    -- ORA-11534: check constraint violated for domain POSITIVE_AMOUNT
    DBMS_OUTPUT.PUT_LINE('Domain error: ' || SQLCODE || ' - ' || SQLERRM);
END;
/


-- Oracle 23ai: IF NOT EXISTS eliminates entire error categories
-- No more ORA-00955 for CREATE or ORA-00942 for DROP
CREATE TABLE IF NOT EXISTS staging_temp (
  id   NUMBER,
  data CLOB
);
-- No error if table already exists β€” 19c would raise ORA-00955

DROP TABLE IF EXISTS staging_temp;
-- No error if table does not exist β€” 19c would raise ORA-00942


-- Oracle 23ai: SQL Firewall configuration for error-raising procedures
-- Run in observation mode first to capture baseline
BEGIN
  -- Enable SQL Firewall in observation mode
  DBMS_SQL_FIREWALL.ENABLE;
  
  -- Start capturing baseline for application user
  DBMS_SQL_FIREWALL.CREATE_CAPTURE(
    username      => 'APP_USER',
    top_level_only => FALSE,  -- capture nested calls too
    exclude_users  => SYS.DBMS_SQL_FIREWALL.USER_LIST('SYS','SYSTEM')
  );
  DBMS_SQL_FIREWALL.START_CAPTURE(username => 'APP_USER');
  
  -- After a full business cycle, stop capture and generate allow list
  -- DBMS_SQL_FIREWALL.STOP_CAPTURE(username => 'APP_USER');
  -- DBMS_SQL_FIREWALL.GENERATE_ALLOW_LIST(username => 'APP_USER');
  -- DBMS_SQL_FIREWALL.ENABLE_ALLOW_LIST(username => 'APP_USER');
END;
/

-- Check for firewall violations that might be false positives
-- from legitimate error-raising procedures
SELECT username, sql_text, firewall_action, occurred_at
FROM   DBA_SQL_FIREWALL_VIOLATIONS
WHERE  username = 'APP_USER'
AND    UPPER(sql_text) LIKE '%RAISE_APPLICATION_ERROR%'
ORDER BY occurred_at DESC;
⚠ 23ai Breaking Changes for Error Handling β€” Read Before Upgrading
πŸ“Š Production Insight
23ai implicit conversion changes are the number-one cause of post-upgrade production incidents. In every 19c-to-23ai migration I have been involved with, the conversion issue was the first thing that broke. Fix it on 19c before upgrading: add TRIM and explicit format masks, run regression tests, confirm no behavioral change on 19c, then upgrade. This approach isolates the conversion fix from the version change and makes root cause analysis dramatically easier if other issues arise during the upgrade. SQL Firewall performance overhead (5–15 ms per statement in capture mode) is measurable in latency-sensitive applications β€” benchmark before enabling in production. JSON error message format changes will break SQLERRM parsing in monitoring tools β€” update your parsing patterns before cutover.
🎯 Key Takeaway
23ai is the most significant shift in Oracle error handling behavior in over a decade. Stricter implicit conversion breaks previously error-free code. JSON diagnostics are richer but change error message format. SQL Firewall adds a new interception layer with measurable performance overhead. Fix implicit conversions on 19c before upgrading. Capture SQL Firewall baselines for a full business cycle before enforcement. Test every error handling path on 23ai before production cutover.
23ai Migration Error Handling Checklist
IfCode uses implicit VARCHAR2-to-NUMBER or VARCHAR2-to-DATE conversion without format masks
β†’
UseCritical risk. Audit DBA_SOURCE for all TO_NUMBER and TO_DATE calls without format masks. Add TRIM + explicit format masks. Fix on 19c first, test, then upgrade.
IfCode uses JSON_TABLE, JSON_VALUE, JSON_QUERY without explicit error clauses
β†’
UseHigh risk. Add explicit ERROR ON ERROR, NULL ON ERROR, or DEFAULT ... ON ERROR clauses to every JSON function call. Test with malformed JSON inputs on 23ai.
IfCode or monitoring tools parse ORA-06512 stack traces or SQLERRM text for error classification
β†’
UseMedium risk. Retest all parsing patterns against actual 23ai error output. Stack depth may differ due to compiler optimizations. JSON error messages include additional context fields.
IfCode uses RAISE_APPLICATION_ERROR for business logic validation flow control
β†’
UseMedium risk. Whitelist these procedures in SQL Firewall baseline capture. Run capture for a full business cycle to ensure all error-raising paths are included in the allow list.
IfCode depends on exact FORMAT_ERROR_BACKTRACE line numbers for error correlation
β†’
UseMedium risk. Retest on 23ai β€” compiler inlining may eliminate intermediate stack frames. Parse on object name rather than line number for version-agnostic correlation.
IfDeployment scripts use CREATE TABLE, CREATE INDEX without IF NOT EXISTS
β†’
UseLow risk. Existing scripts work fine on 23ai. Optionally add IF NOT EXISTS for cleaner deployment on 23ai (but this breaks backward compatibility with 19c).

ORA-06512 Format Consistency Across Versions

The ORA-06512 message format itself is one of the most stable aspects of Oracle's error infrastructure. Across 19c, 21c, and 23ai, the format remains: ORA-06512: at "[OWNER.]OBJECT_NAME", line [N]. The syntax has not changed. The quoting style has not changed. The owner.object naming convention has not changed. Code that parses ORA-06512 text using REGEXP_SUBSTR or INSTR operations will produce the same results on all three versions for the message format itself.

What can differ is the content of the stack. The number of ORA-06512 lines (stack depth) may vary between versions because the PL/SQL compiler in 21c and 23ai applies more aggressive optimization. Intermediate procedure calls that the compiler inlines will not appear as separate stack frames. A call chain of A β†’ B β†’ C β†’ D that produces four ORA-06512 lines on 19c might produce only three on 23ai if the compiler inlined procedure C into B.

SQL macro expansion also affects stack content. When a SQL macro expands inline, errors within the expanded SQL report line numbers relative to the expansion site in the calling procedure, not the macro definition. This means the same logical error can report different line numbers depending on where the macro is called from.

For production systems, the recommendation is to parse ORA-06512 stacks using object name matching rather than line number matching or frame counting. Object names are stable across versions and across compilation optimization levels. Line numbers are useful for debugging but should not be relied upon for automated error correlation in cross-version environments.

ora06512_parsing_across_versions.sql Β· SQL
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
-- Version-agnostic ORA-06512 stack parsing function
-- Works identically on 19c, 21c, and 23ai
CREATE OR REPLACE FUNCTION parse_error_stack(
  p_error_stack IN CLOB
) RETURN SYS_REFCURSOR IS
  v_cursor SYS_REFCURSOR;
BEGIN
  OPEN v_cursor FOR
    WITH stack_lines AS (
      SELECT
        REGEXP_SUBSTR(
          p_error_stack,
          'ORA-06512:[^' || CHR(10) || ']+',
          1, LEVEL
        ) AS line_text,
        LEVEL AS stack_position
      FROM dual
      CONNECT BY REGEXP_SUBSTR(
        p_error_stack,
        'ORA-06512:[^' || CHR(10) || ']+',
        1, LEVEL
      ) IS NOT NULL
    )
    SELECT
      stack_position,
      REGEXP_SUBSTR(line_text, 'at "([^"]+)"', 1, 1, NULL, 1) AS object_name,
      TO_NUMBER(
        REGEXP_SUBSTR(line_text, 'line (\d+)', 1, 1, NULL, 1)
      ) AS line_number,
      line_text AS raw_text
    FROM stack_lines
    ORDER BY stack_position;
  
  RETURN v_cursor;
END parse_error_stack;
/


-- Demonstration: parse a multi-frame stack trace
DECLARE
  v_stack CLOB :=
    'ORA-01403: no data found' || CHR(10) ||
    'ORA-06512: at "PAYMENTS.CURRENCY_CONVERT", line 42' || CHR(10) ||
    'ORA-06512: at "PAYMENTS.BATCH_PROCESSOR", line 187' || CHR(10) ||
    'ORA-06512: at "PAYMENTS.NIGHTLY_JOB", line 15' || CHR(10) ||
    'ORA-06512: at line 1';
  v_cursor  SYS_REFCURSOR;
  v_pos     NUMBER;
  v_object  VARCHAR2(261);
  v_line    NUMBER;
  v_raw     VARCHAR2(4000);
BEGIN
  v_cursor := parse_error_stack(v_stack);
  DBMS_OUTPUT.PUT_LINE('Pos  Object Name                      Line');
  DBMS_OUTPUT.PUT_LINE('---  -------------------------------  ----');
  LOOP
    FETCH v_cursor INTO v_pos, v_object, v_line, v_raw;
    EXIT WHEN v_cursor%NOTFOUND;
    DBMS_OUTPUT.PUT_LINE(
      LPAD(v_pos, 3) || '  ' ||
      RPAD(NVL(v_object, '<anonymous>'), 33) ||
      NVL(TO_CHAR(v_line), 'N/A')
    );
  END LOOP;
  CLOSE v_cursor;
END;
/
-- Output (identical on 19c, 21c, 23ai):
-- Pos  Object Name                      Line
-- ---  -------------------------------  ----
--   1  PAYMENTS.CURRENCY_CONVERT        42
--   2  PAYMENTS.BATCH_PROCESSOR         187
--   3  PAYMENTS.NIGHTLY_JOB             15
--   4  <anonymous>                      1


-- Version detection for conditional error handling
-- Useful when the same codebase must run on multiple versions
CREATE OR REPLACE FUNCTION get_oracle_major_version
  RETURN NUMBER IS
  v_version VARCHAR2(50);
BEGIN
  SELECT version_full INTO v_version FROM v$instance;
  RETURN TO_NUMBER(REGEXP_SUBSTR(v_version, '^\d+'));
END get_oracle_major_version;
/

-- Example: version-aware error handler
CREATE OR REPLACE PROCEDURE version_aware_handler IS
  v_major NUMBER := get_oracle_major_version;
BEGIN
  -- business logic here
  NULL;
EXCEPTION
  WHEN OTHERS THEN
    IF v_major >= 23 THEN
      -- 23ai provides richer diagnostics β€” log them
      DBMS_OUTPUT.PUT_LINE('23ai stack: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    ELSE
      -- 19c/21c standard handling
      DBMS_OUTPUT.PUT_LINE('Stack: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    END IF;
    RAISE;
END version_aware_handler;
/
πŸ”₯Stack Trace Parsing Best Practices Across Versions
  • ORA-06512 message format is byte-for-byte stable across 19c, 21c, and 23ai β€” your REGEXP patterns for the message itself will not break
  • Stack depth (number of ORA-06512 lines) may vary due to compiler inlining in 21c and 23ai β€” do not count frames for error identification
  • Line numbers may differ for the same code if the compiler optimizes differently between versions β€” match on object name, not line number, for automated correlation
  • SQL macro expansion shifts line numbers to the expansion site β€” the macro name does not appear in the ORA-06512 stack
  • Always use DBMS_UTILITY.FORMAT_ERROR_BACKTRACE instead of parsing SQLERRM β€” SQLERRM is truncated to 512 bytes and loses stack depth information
πŸ“Š Production Insight
ORA-06512 format stability is the good news. The bad news is that stack depth and line number accuracy are version-dependent. If your monitoring system counts ORA-06512 frames to identify the error location (frame 1 = innermost failure), it will work on all versions β€” but the total frame count may differ. If your system matches on exact line numbers to correlate errors with known issues, it will break when you upgrade because the compiler may inline calls differently. Parse on object name for automated correlation. Use line numbers only for human debugging.
🎯 Key Takeaway
ORA-06512 message format is consistent across 19c, 21c, and 23ai β€” the same REGEXP_SUBSTR pattern works on all versions. Stack depth may differ due to compiler optimizations. Line numbers may shift due to SQL macro expansion and inlining. Parse on object name for automated error correlation. Use DBMS_UTILITY.FORMAT_ERROR_BACKTRACE instead of SQLERRM for complete stack information.

PL/SQL Language Feature Changes Affecting Error Paths

Each Oracle version introduced PL/SQL language features that create new error conditions, eliminate existing ones, or change the paths through which errors propagate. Understanding these feature-driven changes is essential for upgrade planning because they affect what errors your code can encounter, not just how errors are reported.

Oracle 19c introduced qualified expressions for collections and polymorphic table functions. Qualified expressions reduce ORA-06531 (Reference to uninitialized collection) errors by enabling inline initialization. Polymorphic table functions create new error paths related to type descriptor validation. These are positive changes that reduce error surface area, but they mean that ORA-06531 handlers in pre-19c code may become dead code.

Oracle 21c introduced JSON duality views, which present relational data as JSON documents and allow DML through the JSON representation. Errors in duality view operations report new ORA codes with JSON path information in the error message β€” a format that does not exist in 19c error messages. If your error logging or monitoring parses SQLERRM text, duality view errors will not match existing patterns.

Oracle 23ai introduced several features that directly change error behavior. IF NOT EXISTS for DDL statements eliminates ORA-00955 (name already used by an existing object) and ORA-00942 (table or view does not exist) for guarded DDL operations. This is a pure reduction in error surface area β€” code that previously needed exception handlers for these errors no longer needs them on 23ai. SQL domains introduce a new constraint violation error code that is distinct from the standard ORA-02290 (check constraint violated). Annotations provide metadata for documentation purposes but do not change runtime error behavior.

The cumulative effect of these changes across three versions is that the set of error codes your application can encounter differs depending on the Oracle version. A comprehensive error handling package must declare PRAGMA EXCEPTION_INIT bindings for version-specific error codes and include handlers for error conditions that exist on some versions but not others.

plsql_feature_error_paths.sql Β· SQL
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
-- 19c: Qualified expressions reduce ORA-06531 risk
-- Before 19c, this pattern was common and error-prone:
DECLARE
  TYPE t_ids IS TABLE OF NUMBER;
  v_ids t_ids;  -- uninitialized!
BEGIN
  v_ids.EXTEND;  -- ORA-06531: Reference to uninitialized collection
  v_ids(1) := 42;
EXCEPTION
  WHEN COLLECTION_IS_NULL THEN
    DBMS_OUTPUT.PUT_LINE('Forgot to initialize: ' || SQLERRM);
END;
/

-- 19c qualified expression eliminates the error entirely:
DECLARE
  TYPE t_ids IS TABLE OF NUMBER;
  v_ids t_ids := t_ids(42, 99, 157);  -- initialized inline
BEGIN
  FOR i IN 1..v_ids.COUNT LOOP
    DBMS_OUTPUT.PUT_LINE('ID: ' || v_ids(i));
  END LOOP;
  -- COLLECTION_IS_NULL handler is now dead code
END;
/


-- 21c: JSON Duality View errors include JSON path diagnostics
CREATE OR REPLACE JSON RELATIONAL DUALITY VIEW order_dv AS
  orders @INSERT @UPDATE @DELETE {
    _id        : order_id,
    customerId : customer_id,
    orderDate  : order_date,
    amount     : amount,
    items      : order_items @INSERT @UPDATE @DELETE [{
      itemId    : item_id,
      productId : product_id,
      quantity  : quantity,
      unitPrice : unit_price
    }]
  };
/

-- DML through duality view produces errors with JSON path context
BEGIN
  INSERT INTO order_dv
  VALUES ('{"_id": 1, "customerId": null, "amount": 100}');
  -- If customer_id has a NOT NULL constraint:
  -- ORA-02290: check constraint (SCHEMA.SYS_C00XXXX) violated
  -- with additional JSON context: at path $.customerId
EXCEPTION
  WHEN OTHERS THEN
    -- SQLERRM includes JSON path information on 21c/23ai
    -- This format does not exist on 19c
    DBMS_OUTPUT.PUT_LINE('Duality error: ' || SQLERRM);
    DBMS_OUTPUT.PUT_LINE('Stack: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
END;
/


-- 23ai: IF NOT EXISTS eliminates entire error categories
-- Before 23ai, you needed this:
BEGIN
  EXECUTE IMMEDIATE 'CREATE TABLE staging_temp (id NUMBER, data CLOB)';
EXCEPTION
  WHEN OTHERS THEN
    IF SQLCODE = -955 THEN
      NULL;  -- table already exists, ignore
    ELSE
      RAISE;
    END IF;
END;
/

-- 23ai: no exception handler needed
CREATE TABLE IF NOT EXISTS staging_temp (id NUMBER, data CLOB);
-- ORA-00955 is impossible with IF NOT EXISTS
-- The exception handler above is now dead code on 23ai


-- 23ai: SQL Domain constraint errors β€” new error code
CREATE DOMAIN email_address AS VARCHAR2(320)
  CONSTRAINT email_format_chk
    CHECK (REGEXP_LIKE(email_address, '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'));

CREATE TABLE contacts_23ai (
  contact_id NUMBER GENERATED ALWAYS AS IDENTITY,
  email      email_address
);

DECLARE
  e_domain_violation EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_domain_violation, -11534);
BEGIN
  INSERT INTO contacts_23ai (email) VALUES ('not-an-email');
EXCEPTION
  WHEN e_domain_violation THEN
    -- ORA-11534: domain constraint violated β€” NOT the same as ORA-02290
    DBMS_OUTPUT.PUT_LINE('Domain violation: ' || SQLERRM);
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('Other error: ' || SQLCODE || ' - ' || SQLERRM);
    RAISE;
END;
/


-- Version-aware error handling package skeleton
CREATE OR REPLACE PACKAGE error_handler_pkg IS
  -- Standard errors (all versions)
  e_no_data         EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_no_data, -1403);
  
  e_value_error     EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_value_error, -6502);
  
  e_dup_val         EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_dup_val, -1);
  
  -- 21c+ errors
  e_immutable_dml   EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_immutable_dml, -43923);
  
  -- 23ai+ errors
  e_domain_violation EXCEPTION;
  PRAGMA EXCEPTION_INIT(e_domain_violation, -11534);
  
  PROCEDURE handle_and_log(
    p_context IN VARCHAR2,
    p_reraise IN BOOLEAN DEFAULT TRUE
  );
END error_handler_pkg;
/

CREATE OR REPLACE PACKAGE BODY error_handler_pkg IS
  PROCEDURE handle_and_log(
    p_context IN VARCHAR2,
    p_reraise IN BOOLEAN DEFAULT TRUE
  ) IS
  BEGIN
    -- Log with full diagnostics available on the running version
    INSERT INTO error_log (error_timestamp, context, error_code,
                           error_message, error_backtrace)
    VALUES (SYSTIMESTAMP, p_context, SQLCODE,
            SQLERRM, DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
    COMMIT;
    
    IF p_reraise THEN
      RAISE;
    END IF;
  END handle_and_log;
END error_handler_pkg;
/
πŸ’‘Feature-Driven Error Path Changes Summary
  • 19c qualified expressions reduce ORA-06531 (uninitialized collection) errors β€” existing COLLECTION_IS_NULL handlers may become dead code
  • 21c JSON duality view errors include JSON path context in SQLERRM β€” update error message parsing patterns
  • 21c immutable tables introduce ORA-43923 β€” add PRAGMA EXCEPTION_INIT declaration to your error handling packages
  • 23ai IF NOT EXISTS eliminates ORA-00955 and ORA-00942 for guarded DDL β€” simplifies deployment scripts but is not backward-compatible
  • 23ai SQL domains introduce ORA-11534 (domain constraint violated) β€” distinct from ORA-02290 (check constraint violated)
  • Build a version-aware error handling package with PRAGMA declarations for all version-specific error codes
πŸ“Š Production Insight
New features create new error conditions not present in older versions. The most operationally dangerous pattern is a WHEN OTHERS THEN NULL handler that silently swallows a new error code your team has never seen before. When you encounter a 21c or 23ai-specific error for the first time in production at 3 AM, you want a named exception with a specific handler and a logged error message β€” not a silent swallow. Add PRAGMA EXCEPTION_INIT declarations for ORA-43923 and ORA-11534 to your error handling packages before upgrading, even if you are not using immutable tables or domains yet. The cost is zero, and it prevents the silent-swallow scenario.
🎯 Key Takeaway
Each Oracle version introduces features that change the set of possible errors. 19c reduced collection initialization errors. 21c added immutable table and duality view errors. 23ai added domain constraint errors and eliminated DDL existence errors. Build a version-aware error handling package that declares all version-specific error codes with PRAGMA EXCEPTION_INIT β€” this is cheap insurance against silent error swallowing during upgrades.

Upgrade Testing Strategy for Error Handling

A systematic approach to testing error handling code across Oracle versions is the difference between a smooth upgrade and a 3 AM incident. The strategy has four phases: inventory, baseline, execution, and comparison.

Phase 1 (Inventory) identifies all error handling code in your application. Query DBA_SOURCE for EXCEPTION, WHEN OTHERS, PRAGMA EXCEPTION_INIT, RAISE, RAISE_APPLICATION_ERROR, FORMAT_ERROR_BACKTRACE, and FORMAT_ERROR_STACK. This gives you the complete map of error handling touchpoints.

Phase 2 (Baseline) captures the actual error behavior on your current version. For each identified error handling path, create a test case that triggers the error condition, captures SQLCODE, SQLERRM, and FORMAT_ERROR_BACKTRACE, and stores the results. This is your regression baseline.

Phase 3 (Execution) runs the same test cases on the target version. The test harness must be identical β€” same test data, same execution order, same session parameters.

Phase 4 (Comparison) compares the baseline results against the target results. Any difference in SQLCODE, error message format, or stack trace depth is a behavioral change that requires investigation. Not all differences are problems β€” some are improvements β€” but all must be reviewed.

The most important test cases are implicit conversion paths (TO_NUMBER, TO_DATE without format masks), JSON function calls without error clauses, and WHEN OTHERS handlers that swallow errors silently. These three categories account for over 80% of post-upgrade error handling issues.

upgrade_test_framework.sql Β· SQL
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
-- Phase 1: Inventory β€” find all error handling code
-- Run on source version to build the test map

-- Find all exception handlers
SELECT owner, name, type, line, TRIM(text) AS text
FROM   dba_source
WHERE  UPPER(text) LIKE '%EXCEPTION%'
AND    UPPER(text) NOT LIKE '%--%EXCEPTION%'  -- exclude comments
AND    owner NOT IN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS','WMSYS')
ORDER BY owner, name, line;

-- Find all WHEN OTHERS handlers (highest risk for silent swallowing)
SELECT owner, name, type, line, TRIM(text) AS text
FROM   dba_source
WHERE  UPPER(text) LIKE '%WHEN OTHERS%'
AND    owner NOT IN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS','WMSYS')
ORDER BY owner, name, line;

-- Find all implicit conversions (highest risk for 23ai breakage)
SELECT owner, name, type, line, TRIM(text) AS text
FROM   dba_source
WHERE  REGEXP_LIKE(text, 'TO_NUMBER\s*\([^,)'']+\)', 'i')  -- no format mask
AND    owner NOT IN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS','WMSYS')
ORDER BY owner, name, line;


-- Phase 2 & 3: Test harness β€” captures error behavior for comparison
CREATE TABLE error_test_results (
  test_id        NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  test_name      VARCHAR2(200)   NOT NULL,
  oracle_version VARCHAR2(50)    NOT NULL,
  test_input     VARCHAR2(4000),
  expected_error VARCHAR2(20),
  actual_sqlcode NUMBER,
  actual_sqlerrm VARCHAR2(4000),
  actual_stack   CLOB,
  pass_fail      VARCHAR2(4)     NOT NULL,
  executed_at    TIMESTAMP       DEFAULT SYSTIMESTAMP
);

CREATE OR REPLACE PROCEDURE run_upgrade_error_tests IS
  v_version VARCHAR2(50);
  
  PROCEDURE log_result(
    p_test_name  IN VARCHAR2,
    p_input      IN VARCHAR2,
    p_expected   IN VARCHAR2,
    p_sqlcode    IN NUMBER,
    p_sqlerrm    IN VARCHAR2,
    p_stack      IN CLOB
  ) IS
    v_status VARCHAR2(4);
  BEGIN
    IF (p_expected = 'SUCCESS' AND p_sqlcode = 0)
    OR (p_expected = TO_CHAR(p_sqlcode)) THEN
      v_status := 'PASS';
    ELSE
      v_status := 'FAIL';
    END IF;
    
    INSERT INTO error_test_results (
      test_name, oracle_version, test_input,
      expected_error, actual_sqlcode, actual_sqlerrm,
      actual_stack, pass_fail
    ) VALUES (
      p_test_name, v_version, p_input,
      p_expected, p_sqlcode, p_sqlerrm,
      p_stack, v_status
    );
  END log_result;
  
BEGIN
  SELECT version_full INTO v_version FROM v$instance;
  
  -- Test 1: Implicit conversion with trailing whitespace
  DECLARE
    v_result NUMBER;
    v_code   NUMBER := 0;
    v_msg    VARCHAR2(4000) := 'SUCCESS';
    v_stack  CLOB := NULL;
  BEGIN
    v_result := TO_NUMBER('123.45  ');
  EXCEPTION
    WHEN OTHERS THEN
      v_code  := SQLCODE;
      v_msg   := SQLERRM;
      v_stack := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
  END;
  log_result('IMPLICIT_CONV_TRAILING_SPACE', '123.45  ', 'SUCCESS',
             v_code, v_msg, v_stack);
  
  -- Test 2: Implicit conversion with leading whitespace
  DECLARE
    v_result NUMBER;
    v_code   NUMBER := 0;
    v_msg    VARCHAR2(4000) := 'SUCCESS';
    v_stack  CLOB := NULL;
  BEGIN
    v_result := TO_NUMBER('  123.45');
  EXCEPTION
    WHEN OTHERS THEN
      v_code  := SQLCODE;
      v_msg   := SQLERRM;
      v_stack := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
  END;
  log_result('IMPLICIT_CONV_LEADING_SPACE', '  123.45', 'SUCCESS',
             v_code, v_msg, v_stack);
  
  -- Test 3: Implicit conversion with empty string
  DECLARE
    v_result NUMBER;
    v_code   NUMBER := 0;
    v_msg    VARCHAR2(4000) := 'SUCCESS';
    v_stack  CLOB := NULL;
  BEGIN
    v_result := TO_NUMBER('');
  EXCEPTION
    WHEN OTHERS THEN
      v_code  := SQLCODE;
      v_msg   := SQLERRM;
      v_stack := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
  END;
  log_result('IMPLICIT_CONV_EMPTY_STRING', '', 'SUCCESS',
             v_code, v_msg, v_stack);
  
  -- Test 4: FORMAT_ERROR_BACKTRACE depth
  DECLARE
    v_stack CLOB;
    v_depth NUMBER;
  BEGIN
    BEGIN
      RAISE NO_DATA_FOUND;
    EXCEPTION
      WHEN OTHERS THEN
        v_stack := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
        SELECT COUNT(*) INTO v_depth
        FROM dual
        CONNECT BY REGEXP_SUBSTR(v_stack, 'ORA-06512', 1, LEVEL) IS NOT NULL;
    END;
    log_result('BACKTRACE_DEPTH', NULL, NULL,
               v_depth, 'Depth: ' || v_depth, v_stack);
  END;
  
  COMMIT;
END run_upgrade_error_tests;
/

-- Execute on source version
BEGIN run_upgrade_error_tests; END;
/

-- Execute on target version (same procedure, same test data)
BEGIN run_upgrade_error_tests; END;
/


-- Phase 4: Cross-version comparison report
-- Run this after executing tests on both versions
SELECT
  t1.test_name,
  t1.oracle_version   AS source_version,
  t1.actual_sqlcode    AS source_sqlcode,
  SUBSTR(t1.actual_sqlerrm, 1, 80) AS source_error,
  t2.oracle_version    AS target_version,
  t2.actual_sqlcode    AS target_sqlcode,
  SUBSTR(t2.actual_sqlerrm, 1, 80) AS target_error,
  CASE
    WHEN t1.actual_sqlcode = t2.actual_sqlcode THEN 'CONSISTENT'
    ELSE '*** BEHAVIOR CHANGED ***'
  END AS comparison_status
FROM   error_test_results t1
JOIN   error_test_results t2
  ON   t1.test_name = t2.test_name
  AND  t1.oracle_version != t2.oracle_version
WHERE  t1.oracle_version < t2.oracle_version  -- source before target
ORDER BY
  CASE WHEN t1.actual_sqlcode != t2.actual_sqlcode THEN 0 ELSE 1 END,
  t1.test_name;
⚠ Upgrade Testing Is Not Optional
πŸ“Š Production Insight
Upgrade testing must include error handling paths, not just happy paths. The most common failure mode is a team that tests 500 successful transactions on the target version, declares the upgrade ready, and then encounters 47 conversion errors on the first batch run because the batch processes edge-case data that the 500 test transactions did not cover. Include edge-case data in your test harness: trailing whitespace, leading whitespace, empty strings, NULL values, very large numbers, very small numbers, negative zero, and NLS-specific decimal separators. The implicit conversion tests alone will catch the majority of 19c-to-23ai upgrade issues.
🎯 Key Takeaway
Test every error handling path on the target version before production cutover. Create a structured test harness that captures SQLCODE, SQLERRM, and FORMAT_ERROR_BACKTRACE for each test case. Compare results between source and target versions. Flag any SQLCODE difference as a behavioral change requiring investigation. Automate the test harness for continuous regression testing. Implicit conversion tests with edge-case data catch the majority of 19c-to-23ai issues.
Upgrade Testing Scope by Change Magnitude
IfPatch upgrade within same version (e.g., 19.18 to 19.23)
β†’
UseTest critical error paths only β€” implicit conversion, NO_DATA_FOUND, and VALUE_ERROR handlers. Behavior changes in patches are rare but have occurred (notably in JSON handling).
IfInnovation release upgrade (e.g., 19c to 21c)
β†’
UseTest all error handling paths plus immutable table DML, SQL macro line numbers, and automatic indexing effects on error paths. Add PRAGMA EXCEPTION_INIT for ORA-43923.
IfMajor LTS upgrade (e.g., 19c to 23ai)
β†’
UseFull regression test suite: implicit conversions, JSON error handling, BOOLEAN behavior, domain constraints, SQL Firewall interaction, FORMAT_ERROR_BACKTRACE depth, and SQLERRM format changes. This is the highest-risk upgrade path.
IfCross-platform migration (e.g., on-premises to OCI, or Linux to Exadata)
β†’
UseAll of the above plus NLS parameter differences (NLS_NUMERIC_CHARACTERS, NLS_DATE_FORMAT, NLS_TERRITORY), file system path differences in UTL_FILE operations, and network timeout differences affecting DBMS_SCHEDULER error handling.
πŸ—‚ Oracle Version Error Handling Feature Comparison
Side-by-side differences across 19c, 21c, and 23ai that affect error handling paths, diagnostic detail, and PL/SQL exception behavior
FeatureOracle 19cOracle 21cOracle 23ai
Implicit VARCHAR2-to-NUMBER conversionPermissive β€” trailing and leading whitespace silently trimmedSame permissive behavior as 19cStrict β€” trailing whitespace raises ORA-06502
ORA-06512 message formatat "OWNER.OBJECT", line NIdentical to 19cIdentical to 19c
FORMAT_ERROR_BACKTRACE depthFull line numbers, all frames preservedFull line numbers, all frames preservedFull line numbers but compiler inlining may eliminate intermediate frames
JSON error reportingBasic error messages β€” error code onlyEnhanced with native JSON type contextDetailed: JSON path, character position, expected vs actual type
BOOLEAN in SQLNot supported β€” PL/SQL onlyLimited support in some contextsFull native SQL support β€” changes NULL handling in SQL-context evaluations
SQL FirewallNot availableNot availableAvailable β€” observation and blocking modes, 5–15 ms overhead per statement
Immutable tablesNot availableAvailable β€” ORA-43923 for UPDATE/DELETEAvailable β€” same behavior as 21c
IF NOT EXISTS DDLNot available β€” requires exception handler for ORA-00955Not availableAvailable β€” eliminates ORA-00955 and ORA-00942 for guarded DDL
SQL DomainsNot availableNot availableAvailable β€” ORA-11534 for domain constraint violations (distinct from ORA-02290)
SQL MacrosNot availableAvailable β€” line numbers shift to expansion site in ORA-06512 stacksAvailable β€” same behavior as 21c
JSON Duality ViewsNot availableAvailable β€” errors include JSON path context in SQLERRMAvailable β€” enhanced diagnostics over 21c
Qualified expressionsAvailable β€” reduces ORA-06531 initialization errorsAvailableAvailable
PLSQL_OPTIMIZE_LEVEL impactStable stack traces at all levelsOptimization may inline simple callsMore aggressive inlining β€” stack depth may decrease at levels 2 and 3
Support statusLong-term support through April 2027Innovation release β€” limited support windowLong-term support β€” current production target

🎯 Key Takeaways

  • ORA-06512 message format is stable across 19c, 21c, and 23ai β€” but the errors that trigger it, the stack depth, and the surrounding diagnostics have all changed
  • 23ai enforces stricter implicit conversion: trailing whitespace in TO_NUMBER now raises ORA-06502 β€” fix with TRIM + explicit format masks on 19c before upgrading
  • JSON error reporting in 23ai includes path expression, character position, and expected-vs-actual type β€” update SQLERRM parsing patterns in monitoring tools
  • SQL Firewall in 23ai adds 5–15 ms overhead and may flag RAISE_APPLICATION_ERROR calls as anomalous β€” capture baselines for a full business cycle before enforcement
  • 21c introduced immutable tables (ORA-43923) and SQL macros (line number shifts in backtraces) β€” both carry forward into 23ai
  • 23ai SQL domains introduce ORA-11534 (domain constraint violated) β€” distinct from ORA-02290 and requires new PRAGMA EXCEPTION_INIT declarations
  • Compiler optimizations in 21c and 23ai may inline calls and reduce FORMAT_ERROR_BACKTRACE depth β€” parse on object name, not frame count or line number
  • Always test error handling paths β€” not just happy paths β€” on the target version before production cutover

⚠ Common Mistakes to Avoid

    βœ•Assuming ORA-06512 behavior is identical across all Oracle versions
    Symptom

    Code that works on 19c produces different errors on 23ai. Stack traces have different depths. Error handling code fails silently because WHEN OTHERS swallows new error codes. Monitoring alerts miss version-specific errors because SQLCODE filters do not include new codes like -43923 or -11534.

    Fix

    Build a regression test suite that captures SQLCODE, SQLERRM, and FORMAT_ERROR_BACKTRACE for every error handling path. Run the suite on both source and target versions. Flag any SQLCODE difference as a behavioral change. Add PRAGMA EXCEPTION_INIT declarations for version-specific error codes (ORA-43923, ORA-11534) to your error handling packages before upgrading.

    βœ•Not auditing implicit conversion code before upgrading to 23ai
    Symptom

    ORA-06502 errors appear on 23ai for code that ran error-free on 19c for years. VARCHAR2 strings with trailing whitespace fail to convert to numbers. Batch jobs that process external data (flat files, API responses, message queues) fail on the first run after upgrade. The ORA-06512 stack points to TO_NUMBER calls that were never problematic before.

    Fix

    Query DBA_SOURCE for all TO_NUMBER and TO_DATE calls without explicit format masks. Add TRIM(BOTH FROM input) and explicit format masks to every identified call. Fix these on 19c first β€” the fix is safe on all versions β€” and verify no behavioral change before upgrading. Test with edge-case data: trailing spaces, leading spaces, tabs, empty strings, NULL values, and NLS-specific decimal separators.

    βœ•Skipping error handling path testing during upgrades because happy-path tests pass
    Symptom

    The upgrade test suite runs 500 successful transactions and the team declares the upgrade ready. First batch run after production cutover produces dozens of errors in code paths that were not exercised during testing. Error handling code catches wrong exceptions or swallows errors silently because WHEN OTHERS masks version-specific behavioral changes.

    Fix

    Create test cases that deliberately trigger every known error condition: NO_DATA_FOUND, TOO_MANY_ROWS, VALUE_ERROR, ZERO_DIVIDE, DUP_VAL_ON_INDEX, and every PRAGMA EXCEPTION_INIT-bound error in your packages. Include edge-case inputs that exercise implicit conversion paths. Run the error test suite on both source and target versions and compare results before production cutover.

    βœ•Not configuring SQL Firewall whitelist before 23ai production deployment
    Symptom

    SQL Firewall flags error-raising procedures as security anomalies. Legitimate RAISE_APPLICATION_ERROR calls in validation logic are logged as violations. False positive alerts flood monitoring systems on the first day of production. In blocking mode, SQL Firewall rejects legitimate business logic operations.

    Fix

    Enable SQL Firewall in observation mode for at least one full business cycle (typically 30 days minimum) before switching to blocking mode. Ensure the baseline capture period exercises all code paths, including error-raising procedures, batch jobs, month-end processing, and exception-driven validation logic. Review DBA_SQL_FIREWALL_VIOLATIONS for false positives before enforcing blocking mode. Whitelist known error-raising procedures explicitly.

    βœ•Parsing ORA-06512 text with hardcoded format assumptions or frame counting
    Symptom

    Stack parsing code breaks when compiler optimizations eliminate intermediate frames on 23ai. Error correlation logic that counts ORA-06512 lines to identify the error location produces wrong results. REGEXP patterns that assume a specific number of capture groups or specific whitespace patterns fail on edge cases.

    Fix

    Use REGEXP_SUBSTR patterns that match on the stable ORA-06512 format: 'at "([^"]+)", line (\d+)'. Match on object name for automated error correlation, not line number or frame count. Use DBMS_UTILITY.FORMAT_ERROR_BACKTRACE instead of parsing SQLERRM β€” SQLERRM is truncated to 512 bytes and loses stack information for deep call chains. Test parsing code against actual error output from each target Oracle version.

    βœ•Using WHEN OTHERS THEN NULL to suppress all exceptions without logging
    Symptom

    New version-specific errors are silently swallowed. Code appears to work on 23ai but produces incorrect results because errors that should have been caught and handled are being suppressed. Data integrity issues accumulate undetected. Debugging becomes impossible because no error information is preserved.

    Fix

    Every WHEN OTHERS handler must either re-raise the exception (RAISE) or log the error details (SQLCODE, SQLERRM, FORMAT_ERROR_BACKTRACE) to a persistent error table before continuing. Search DBA_SOURCE for 'WHEN OTHERS' followed by 'NULL' or no RAISE statement and fix every instance. This is not just an upgrade issue β€” it is a correctness issue on every Oracle version.

Interview Questions on This Topic

  • QWhat are the most significant error handling changes between Oracle 19c and 23ai that would affect a production upgrade?SeniorReveal
    Three changes dominate the upgrade risk profile. First, 23ai enforces stricter implicit data type conversion β€” VARCHAR2 strings with trailing whitespace that TO_NUMBER silently trimmed on 19c now raise ORA-06502. This is the single most common source of post-upgrade errors because virtually every production system processes some form of external data with inconsistent formatting. Second, JSON error reporting in 23ai is substantially enhanced β€” error messages include the JSON path expression, character position, and expected-vs-actual data type, which changes the format of SQLERRM output that monitoring tools may parse. Third, SQL Firewall in 23ai introduces a new interception layer that can flag RAISE_APPLICATION_ERROR calls as anomalous if they were not captured during baseline collection, adding both a performance overhead (5–15 ms per statement) and a risk of false-positive blocking. Beyond these three, SQL domains introduce a new constraint violation error code (ORA-11534) distinct from standard CHECK constraints, and compiler optimizations may reduce FORMAT_ERROR_BACKTRACE stack depth by inlining intermediate calls. Teams must audit implicit conversions (fix with TRIM + format masks on 19c first), update JSON error parsing patterns, capture SQL Firewall baselines for a full business cycle, and test all error handling paths on 23ai before production cutover.
  • QHow would you design a regression test suite to verify error handling behavior across Oracle versions during an upgrade?SeniorReveal
    The approach has four phases. Phase 1 is inventory: query DBA_SOURCE for all exception handlers, PRAGMA EXCEPTION_INIT declarations, RAISE_APPLICATION_ERROR calls, and FORMAT_ERROR_BACKTRACE usage to build a complete map of error handling touchpoints. Phase 2 is baseline capture: for each identified error path, create a test case that deliberately triggers the error condition, then capture SQLCODE, SQLERRM, and FORMAT_ERROR_BACKTRACE into a results table tagged with the Oracle version. Phase 3 is target execution: run the identical test suite on the target version with the same test data and session parameters. Phase 4 is comparison: join the results tables on test name, compare SQLCODE values, and flag any difference as a behavioral change requiring investigation. The highest-priority test cases are implicit conversions (TO_NUMBER, TO_DATE without format masks tested with trailing spaces, leading spaces, empty strings, and NLS-specific separators), JSON function calls with malformed inputs, and WHEN OTHERS handlers with no RAISE statement. Automate the harness so it runs as part of every patch upgrade, not just major versions β€” Oracle has changed behavior in patch releases too.
  • QWhy might FORMAT_ERROR_BACKTRACE return different stack depths on 23ai compared to 19c for the same PL/SQL code?Mid-levelReveal
    The PL/SQL compiler in 23ai applies more aggressive optimization at PLSQL_OPTIMIZE_LEVEL 2 and 3 than the 19c compiler does. Specifically, the 23ai compiler may inline small procedures β€” replacing the CALL instruction with the procedure body at the call site. When a procedure is inlined, it no longer appears as a separate stack frame in FORMAT_ERROR_BACKTRACE, so the output contains fewer ORA-06512 lines. A call chain of A calling B calling C calling D that produces four ORA-06512 frames on 19c might produce only three on 23ai if the compiler inlined procedure C into B. Additionally, SQL macros (introduced in 21c, improved in 23ai) expand at compile time, so errors within the expanded SQL report line numbers relative to the calling procedure, not the macro definition β€” the macro name never appears in the stack. For automated error correlation, match on object name rather than frame count or line number. For debugging, you can temporarily recompile with PLSQL_OPTIMIZE_LEVEL = 1 to disable inlining and get a full-depth stack trace.
  • QWhat is the SQL Firewall in Oracle 23ai and how does it interact with PL/SQL error handling?Mid-levelReveal
    SQL Firewall is a 23ai security feature that monitors all SQL statements executed by a database user against a captured baseline of normal activity. It operates in two modes: observation (log anomalies to DBA_SQL_FIREWALL_VIOLATIONS) and blocking (reject anomalous SQL with an error). For error handling, it has three impacts. First, performance: SQL Firewall adds 5–15 ms overhead per statement in capture mode, which can push latency-sensitive operations past timeout thresholds and produce timeout errors that did not exist before. Second, false positives: procedures that use RAISE_APPLICATION_ERROR for business logic validation β€” a common PL/SQL pattern β€” may be flagged as anomalous if those error-raising code paths were not exercised during baseline capture. In blocking mode, this means legitimate business operations are rejected. Third, monitoring noise: RAISE_APPLICATION_ERROR calls logged as security violations can flood alerting systems. The mitigation is to run SQL Firewall in observation mode for at least one full business cycle, ensure all code paths (including batch jobs, month-end processing, and exception-driven validation) are exercised during capture, and review DBA_SQL_FIREWALL_VIOLATIONS for false positives before switching to blocking mode.
  • QA colleague says they do not need to retest error handling after upgrading from Oracle 19c to 23ai because ORA-06512 has not changed. How would you respond?JuniorReveal
    They are right that ORA-06512 the message has not changed β€” the format 'at OWNER.OBJECT, line N' is identical across all three versions. But ORA-06512 is the stack trace indicator, not the error itself. What has changed is everything that feeds into that stack trace. On 23ai, stricter implicit conversion means TO_NUMBER calls that succeeded silently on 19c now raise ORA-06502, producing ORA-06512 stacks that never existed before. Enhanced JSON diagnostics change the text of SQLERRM for JSON errors, potentially breaking monitoring parsers. SQL Firewall can intercept error-raising statements. SQL domains introduce new constraint error codes. Compiler optimizations may change stack depth. The ORA-06512 format being stable is actually what makes this dangerous β€” the stacks look familiar, so people assume nothing changed, but the errors that trigger them are different. I would recommend running our error handling regression suite on 23ai before production cutover. It typically takes two to three days and has caught behavioral changes in every major version upgrade I have been part of.

Frequently Asked Questions

Is ORA-06512 itself different between Oracle 19c and 23ai?

No. The ORA-06512 message format is byte-for-byte identical across 19c, 21c, and 23ai: ORA-06512: at "OWNER.OBJECT_NAME", line N. What differs is everything around it. The errors that trigger it differ because 23ai enforces stricter implicit conversion (ORA-06502 fires where 19c succeeded silently). The stack depth may differ because 23ai's compiler inlines calls more aggressively, eliminating intermediate ORA-06512 frames. The diagnostic context differs because 23ai JSON errors include path and position detail not present in 19c error messages. The message format is stable; the ecosystem producing and consuming that message has evolved. This stability is actually what makes the change deceptive β€” the stack traces look familiar, so teams assume nothing changed.

Should I upgrade from Oracle 19c directly to 23ai or go through 21c?

Direct upgrade from 19c to 23ai is the standard path and is fully supported by Oracle. Most production environments skip 21c because it is an innovation release without long-term support. Going through 21c adds operational complexity (two upgrades instead of one) without adding support coverage. However, skipping 21c means you encounter both 21c-era changes (immutable tables, SQL macros, automatic indexing) and 23ai-era changes (stricter conversion, SQL Firewall, BOOLEAN, domains) simultaneously, which complicates diagnosis when something breaks. If your risk tolerance is low, consider standing up a 21c test environment β€” not for production deployment, but as an intermediate validation step that isolates 21c-specific changes from 23ai-specific changes. This adds a week to the testing timeline but cuts diagnostic time significantly when you encounter unexpected errors.

How do I find all implicit conversion code that might break on Oracle 23ai?

Query DBA_SOURCE for TO_NUMBER and TO_DATE calls that lack explicit format masks: SELECT owner, name, type, line, text FROM dba_source WHERE REGEXP_LIKE(text, 'TO_NUMBER\s*\([^,)'']+\)', 'i') AND owner NOT IN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS') ORDER BY owner, name, line; β€” this finds TO_NUMBER calls with a single argument (no format mask). Also search for assignment statements where a VARCHAR2 variable is assigned to a NUMBER variable without explicit conversion β€” the compiler performs implicit TO_NUMBER in these cases. Additionally, search for VARCHAR2 columns being compared with NUMBER literals in WHERE clauses: WHERE varchar_col = 123 β€” Oracle performs implicit conversion here too. Add TRIM(BOTH FROM input) and explicit format masks (e.g., '999999999999.99') to every identified conversion. Test with edge-case data: trailing spaces, leading spaces, tab characters, empty strings, NULL values, and NLS-specific decimal separators (comma vs period).

Does PRAGMA EXCEPTION_INIT work differently across Oracle versions?

The PRAGMA EXCEPTION_INIT syntax and binding mechanism are stable across 19c, 21c, and 23ai. You declare a named exception, bind it to an Oracle error number, and use it in a WHEN clause β€” this works identically on all versions. What changes between versions is the set of error numbers you need to bind. Oracle 21c introduced ORA-43923 (immutable table modification), and Oracle 23ai introduced ORA-11534 (domain constraint violated). If your error handling package does not include PRAGMA EXCEPTION_INIT declarations for these new error codes, those errors will fall through to WHEN OTHERS handlers. The fix is to add declarations for all version-specific error codes to your error handling packages before upgrading. The declarations are harmless on versions where the error code cannot occur β€” having a PRAGMA EXCEPTION_INIT for -43923 on 19c has no runtime cost and prevents the silent-swallow problem if you later upgrade to 21c or 23ai.

What Oracle version should new projects target for error handling best practices in 2026?

New projects in 2026 should target Oracle 23ai as the baseline and follow practices that are safe across all versions. Use explicit TRIM and format masks for every data type conversion β€” never rely on implicit conversion, regardless of version. Use JSON functions with explicit error clauses (ERROR ON ERROR, NULL ON ERROR, or DEFAULT ... ON ERROR) on every call. Declare PRAGMA EXCEPTION_INIT bindings for all known version-specific error codes (ORA-43923, ORA-11534) in your error handling package from day one. Never use WHEN OTHERS THEN NULL β€” always log or re-raise. Use IF NOT EXISTS for DDL operations on 23ai. Configure SQL Firewall from project start, capturing a baseline during development and QA. Use SQL domains for type-safe column definitions with built-in constraint enforcement. This forward-compatible approach works on all Oracle versions and eliminates the implicit conversion debt that has caused production incidents on every 19c-to-23ai migration.

How does Oracle 23ai SQL Firewall affect performance and should I enable it in production?

SQL Firewall adds 5–15 ms overhead per SQL statement in capture mode, with lower overhead in enforcement mode once the allow list is generated. For high-throughput OLTP systems processing thousands of statements per second, this overhead is measurable and should be benchmarked against your latency SLAs before enabling. For batch processing and reporting workloads, the overhead is typically negligible. The security benefit is substantial β€” SQL Firewall catches SQL injection patterns and unauthorized query variations that traditional error handling cannot detect. Enable it in observation mode first, run for at least one full business cycle, review violations for false positives, then switch to blocking mode. For error handling specifically, ensure that RAISE_APPLICATION_ERROR calls used for business validation are captured during the baseline period, or they will be flagged as violations.

πŸ”₯
Naren Founder & Author

Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.

← PreviousOracle PL/SQL Performance Tuning to Prevent ORA-06512 TimeoutsNext β†’Migrating Oracle PL/SQL to PostgreSQL – Common Errors
Forged with πŸ”₯ at TheCodeForge.io β€” Where Developers Are Forged