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
Plain-English First
Oracle's error handling has evolved across 19c, 21c, and 23ai in ways that are invisible until you upgrade. ORA-06512 itself — the line that tells you where in the call stack an error propagated — has not changed its format. What has changed is everything around it: the errors that trigger it, the level of detail Oracle provides alongside it, and the PL/SQL language features that create new error conditions entirely. Oracle 23ai is the biggest shift. It tightened implicit data type conversion rules, so a VARCHAR2 value like '123.45 ' (with trailing spaces) that converted to a NUMBER without complaint on 19c now raises ORA-06502. It added richer JSON error diagnostics that include the exact JSON path and character position of the failure. It introduced SQL Firewall, which can intercept and log error-raising statements as potential security anomalies. And it brought native BOOLEAN support into SQL, changing how some conditional expressions behave inside exception blocks. Teams upgrading from 19c to 23ai routinely encounter error behavior changes that look like regressions but are actually stricter correctness enforcement. The fix is always the same pattern: audit your implicit conversion code, add explicit format masks and TRIM calls, test every exception path on the target version, and compare results against your 19c baseline before cutting over to production.
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.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
-- Oracle 19c: implicit conversion succeeds silently-- This is the behavior that breaks on 23aiDECLARE
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);
EXCEPTIONWHEN VALUE_ERROR THEN
DBMS_OUTPUT.PUT_LINE('Value error: ' || SQLERRM);
WHENOTHERSTHEN
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 behaviorCREATEORREPLACEPROCEDURE inner_proc_19c ISBEGINRAISE NO_DATA_FOUND;
END inner_proc_19c;
/
CREATEORREPLACEPROCEDURE middle_proc_19c ISBEGIN
inner_proc_19c;
END middle_proc_19c;
/
CREATEORREPLACEPROCEDURE outer_proc_19c ISBEGIN
middle_proc_19c;
EXCEPTIONWHENOTHERSTHEN
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 riskDECLARETYPE t_amounts ISTABLEOFNUMBERINDEXBY 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);
BEGINFOR i IN1..v_amounts.COUNTLOOP
DBMS_OUTPUT.PUT_LINE('Amount ' || i || ': ' || v_amounts(i));
ENDLOOP;
END;
/
19c as Your Regression Baseline
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.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
-- Oracle 21c: Immutable table introduces new error code ORA-43923CREATEIMMUTABLETABLEaudit_immutable_21c (
event_id NUMBERGENERATEDALWAYSASIDENTITY,
event_type VARCHAR2(50),
event_data CLOB,
created_at TIMESTAMPDEFAULTSYSTIMESTAMP
) NODROPUNTIL365DAYSIDLENODELETEUNTIL730DAYSAFTERINSERT;
-- INSERT works normallyINSERTINTOaudit_immutable_21c (event_type, event_data)
VALUES ('LOGIN', '{"user": "admin", "ip": "10.0.1.15"}');
COMMIT;
-- UPDATE raises new error not present in 19cBEGINUPDATE audit_immutable_21c
SET event_type = 'MODIFIED'WHERE event_id = 1;
EXCEPTIONWHENOTHERSTHEN-- 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 codeDECLARE
e_immutable_violation EXCEPTION;
PRAGMA EXCEPTION_INIT(e_immutable_violation, -43923);
BEGINDELETEFROM audit_immutable_21c WHERE event_id = 1;
EXCEPTIONWHEN e_immutable_violation THEN
DBMS_OUTPUT.PUT_LINE('Cannot modify immutable table - expected behavior');
WHENOTHERSTHEN
DBMS_OUTPUT.PUT_LINE('Unexpected: ' || SQLERRM);
RAISE;
END;
/
-- Oracle 21c: SQL Macro affects ORA-06512 line numbersCREATEORREPLACEFUNCTION active_orders_macro
RETURNVARCHAR2 SQL_MACRO(TABLE) ISBEGINRETURN 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 definitionCREATEORREPLACEPROCEDURE process_active_orders_21c IS
v_total NUMBER;
BEGINSELECTSUM(amount) INTO v_total
FROMactive_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);
EXCEPTIONWHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('No active orders found');
WHENOTHERSTHEN
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 contextDECLARE
v_json JSON := JSON('{"amount": "not_a_number"}');
v_amount NUMBER;
BEGIN
v_amount := JSON_VALUE(v_json, '$.amount'RETURNINGNUMBER);
-- 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.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
-- Oracle 23ai: Stricter implicit conversion — THE most common upgrade breakerDECLARE
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);
EXCEPTIONWHEN 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 versionsEND;
/
-- Oracle 23ai: Enhanced JSON error reporting with path and position detail-- Compare error detail between versionsBEGINDECLARE
v_result NUMBER;
BEGINSELECT JSON_VALUE(
'{"order": {"amount": "not_a_number", "currency": "USD"}}',
'$.order.amount'RETURNINGNUMBERERRORONERROR
) INTO v_result FROM dual;
EXCEPTIONWHENOTHERSTHEN-- 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 pathsCREATEORREPLACEFUNCTIONis_premium_customer(
p_customer_id INNUMBER
) RETURNBOOLEANIS
v_total NUMBER;
BEGINSELECTSUM(amount) INTO v_total
FROM orders
WHERE customer_id = p_customer_id
AND status = 'COMPLETED';
RETURNNVL(v_total, 0) > 10000;
END is_premium_customer;
/
-- 23ai: BOOLEAN function directly in SQL WHERE clause-- This was a syntax error on 19c and 21cSELECT customer_id, name
FROM customers
WHEREis_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 codesCREATEDOMAIN positive_amount ASNUMBER(12,2)
CONSTRAINT positive_amount_chk CHECK (positive_amount > 0)
DISPLAYCASEWHEN positive_amount > 1000THEN TO_CHAR(positive_amount, '999,999.99')
ELSE TO_CHAR(positive_amount, '999.99') ENDORDER positive_amount;
CREATETABLEdomain_orders_23ai (
order_id NUMBERGENERATEDALWAYSASIDENTITY,
amount positive_amount -- domain-typed column
);
-- Domain violation raises a DIFFERENT error code than CHECK constraintBEGININSERTINTOdomain_orders_23ai (amount) VALUES (-50.00);
EXCEPTIONWHENOTHERSTHEN-- 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 DROPCREATETABLEIFNOTEXISTSstaging_temp (
id NUMBER,
data CLOB
);
-- No error if table already exists — 19c would raise ORA-00955DROPTABLEIFEXISTS 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 baselineBEGIN-- 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 proceduresSELECT username, sql_text, firewall_action, occurred_at
FROM DBA_SQL_FIREWALL_VIOLATIONS
WHERE username = 'APP_USER'ANDUPPER(sql_text) LIKE'%RAISE_APPLICATION_ERROR%'ORDERBY occurred_at DESC;
23ai Breaking Changes for Error Handling — Read Before Upgrading
Implicit VARCHAR2-to-NUMBER conversion with trailing whitespace now raises ORA-06502 — this is the single most common source of post-upgrade errors and affects virtually every production system that processes external data
JSON functions return more detailed error messages including path expression, character position, and expected-vs-actual type information — update any code that parses SQLERRM for JSON errors
SQL Firewall can intercept and log error-raising statements as security anomalies — whitelist known error-raising procedures during baseline capture, not after production deployment
BOOLEAN in SQL changes evaluation context for functions used in WHERE clauses — NULL handling follows SQL three-valued logic rather than PL/SQL two-valued logic
SQL domains introduce new constraint error codes distinct from standard CHECK constraint violations (ORA-02290) — add new PRAGMA EXCEPTION_INIT declarations
IF NOT EXISTS eliminates ORA-00955 and ORA-00942 — deployment scripts become simpler but are not backward-compatible with 19c
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.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
-- Version-agnostic ORA-06512 stack parsing function-- Works identically on 19c, 21c, and 23aiCREATEORREPLACEFUNCTIONparse_error_stack(
p_error_stack INCLOB
) RETURN SYS_REFCURSOR IS
v_cursor SYS_REFCURSOR;
BEGINOPEN v_cursor FORWITH stack_lines AS (
SELECT
REGEXP_SUBSTR(
p_error_stack,
'ORA-06512:[^' || CHR(10) || ']+',
1, LEVEL
) AS line_text,
LEVELAS stack_position
FROM dual
CONNECTBY REGEXP_SUBSTR(
p_error_stack,
'ORA-06512:[^' || CHR(10) || ']+',
1, LEVEL
) ISNOTNULL
)
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
ORDERBY stack_position;
RETURN v_cursor;
END parse_error_stack;
/
-- Demonstration: parse a multi-frame stack traceDECLARE
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('--- ------------------------------- ----');LOOPFETCH v_cursor INTO v_pos, v_object, v_line, v_raw;
EXITWHEN v_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(
LPAD(v_pos, 3) || ' ' ||
RPAD(NVL(v_object, '<anonymous>'), 33) ||
NVL(TO_CHAR(v_line), 'N/A')
);
ENDLOOP;
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 versionsCREATEORREPLACEFUNCTION get_oracle_major_version
RETURNNUMBERIS
v_version VARCHAR2(50);
BEGINSELECT 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 handlerCREATEORREPLACEPROCEDURE version_aware_handler IS
v_major NUMBER := get_oracle_major_version;
BEGIN-- business logic hereNULL;
EXCEPTIONWHENOTHERSTHENIF v_major >= 23THEN-- 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);
ENDIF;
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.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
-- 19c: Qualified expressions reduce ORA-06531 risk-- Before 19c, this pattern was common and error-prone:DECLARETYPE t_ids ISTABLEOFNUMBER;
v_ids t_ids; -- uninitialized!BEGIN
v_ids.EXTEND; -- ORA-06531: Reference to uninitialized collectionv_ids(1) := 42;
EXCEPTIONWHEN COLLECTION_IS_NULL THEN
DBMS_OUTPUT.PUT_LINE('Forgot to initialize: ' || SQLERRM);
END;
/
-- 19c qualified expression eliminates the error entirely:DECLARETYPE t_ids ISTABLEOFNUMBER;
v_ids t_ids := t_ids(42, 99, 157); -- initialized inlineBEGINFOR i IN1..v_ids.COUNTLOOP
DBMS_OUTPUT.PUT_LINE('ID: ' || v_ids(i));
ENDLOOP;
-- COLLECTION_IS_NULL handler is now dead codeEND;
/
-- 21c: JSON Duality View errors include JSON path diagnosticsCREATEORREPLACEJSONRELATIONALDUALITYVIEW 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 contextBEGININSERTINTO 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 $.customerIdEXCEPTIONWHENOTHERSTHEN-- 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:BEGINEXECUTEIMMEDIATE'CREATE TABLE staging_temp (id NUMBER, data CLOB)';
EXCEPTIONWHENOTHERSTHENIFSQLCODE = -955THENNULL; -- table already exists, ignoreELSERAISE;
ENDIF;
END;
/
-- 23ai: no exception handler neededCREATETABLEIFNOTEXISTSstaging_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 codeCREATEDOMAIN email_address ASVARCHAR2(320)
CONSTRAINT email_format_chk
CHECK (REGEXP_LIKE(email_address, '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'));
CREATETABLEcontacts_23ai (
contact_id NUMBERGENERATEDALWAYSASIDENTITY,
email email_address
);
DECLARE
e_domain_violation EXCEPTION;
PRAGMA EXCEPTION_INIT(e_domain_violation, -11534);
BEGININSERTINTOcontacts_23ai (email) VALUES ('not-an-email');
EXCEPTIONWHEN e_domain_violation THEN-- ORA-11534: domain constraint violated — NOT the same as ORA-02290
DBMS_OUTPUT.PUT_LINE('Domain violation: ' || SQLERRM);
WHENOTHERSTHEN
DBMS_OUTPUT.PUT_LINE('Other error: ' || SQLCODE || ' - ' || SQLERRM);
RAISE;
END;
/
-- Version-aware error handling package skeletonCREATEORREPLACEPACKAGE 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);
PROCEDUREhandle_and_log(
p_context INVARCHAR2,
p_reraise INBOOLEANDEFAULTTRUE
);
END error_handler_pkg;
/
CREATEORREPLACEPACKAGEBODY error_handler_pkg ISPROCEDUREhandle_and_log(
p_context INVARCHAR2,
p_reraise INBOOLEANDEFAULTTRUE
) ISBEGIN-- Log with full diagnostics available on the running versionINSERTINTOerror_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 THENRAISE;
ENDIF;
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
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.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
-- Phase 1: Inventory — find all error handling code-- Run on source version to build the test map-- Find all exception handlersSELECT owner, name, type, line, TRIM(text) AS text
FROM dba_source
WHEREUPPER(text) LIKE'%EXCEPTION%'ANDUPPER(text) NOTLIKE '%--%EXCEPTION%' -- exclude commentsAND owner NOTIN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS','WMSYS')
ORDERBY 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
WHEREUPPER(text) LIKE'%WHEN OTHERS%'AND owner NOTIN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS','WMSYS')
ORDERBY 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 maskAND owner NOTIN ('SYS','SYSTEM','XDB','MDSYS','CTXSYS','WMSYS')
ORDERBY owner, name, line;
-- Phase 2 & 3: Test harness — captures error behavior for comparisonCREATETABLEerror_test_results (
test_id NUMBERGENERATEDALWAYSASIDENTITYPRIMARYKEY,
test_name VARCHAR2(200) NOTNULL,
oracle_version VARCHAR2(50) NOTNULL,
test_input VARCHAR2(4000),
expected_error VARCHAR2(20),
actual_sqlcode NUMBER,
actual_sqlerrm VARCHAR2(4000),
actual_stack CLOB,
pass_fail VARCHAR2(4) NOTNULL,
executed_at TIMESTAMPDEFAULTSYSTIMESTAMP
);
CREATEORREPLACEPROCEDURE run_upgrade_error_tests IS
v_version VARCHAR2(50);
PROCEDURElog_result(
p_test_name INVARCHAR2,
p_input INVARCHAR2,
p_expected INVARCHAR2,
p_sqlcode INNUMBER,
p_sqlerrm INVARCHAR2,
p_stack INCLOB
) IS
v_status VARCHAR2(4);
BEGINIF (p_expected = 'SUCCESS'AND p_sqlcode = 0)
OR (p_expected = TO_CHAR(p_sqlcode)) THEN
v_status := 'PASS';
ELSE
v_status := 'FAIL';
ENDIF;
INSERTINTOerror_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;
BEGINSELECT version_full INTO v_version FROM v$instance;
-- Test 1: Implicit conversion with trailing whitespaceDECLARE
v_result NUMBER;
v_code NUMBER := 0;
v_msg VARCHAR2(4000) := 'SUCCESS';
v_stack CLOB := NULL;
BEGIN
v_result := TO_NUMBER('123.45 ');
EXCEPTIONWHENOTHERSTHEN
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 whitespaceDECLARE
v_result NUMBER;
v_code NUMBER := 0;
v_msg VARCHAR2(4000) := 'SUCCESS';
v_stack CLOB := NULL;
BEGIN
v_result := TO_NUMBER(' 123.45');
EXCEPTIONWHENOTHERSTHEN
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 stringDECLARE
v_result NUMBER;
v_code NUMBER := 0;
v_msg VARCHAR2(4000) := 'SUCCESS';
v_stack CLOB := NULL;
BEGIN
v_result := TO_NUMBER('');
EXCEPTIONWHENOTHERSTHEN
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 depthDECLARE
v_stack CLOB;
v_depth NUMBER;
BEGINBEGINRAISE NO_DATA_FOUND;
EXCEPTIONWHENOTHERSTHEN
v_stack := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
SELECTCOUNT(*) INTO v_depth
FROM dual
CONNECTBY REGEXP_SUBSTR(v_stack, 'ORA-06512', 1, LEVEL) ISNOTNULL;
END;
log_result('BACKTRACE_DEPTH', NULL, NULL,
v_depth, 'Depth: ' || v_depth, v_stack);
END;
COMMIT;
END run_upgrade_error_tests;
/
-- Execute on source versionBEGIN 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 versionsSELECT
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,
CASEWHEN t1.actual_sqlcode = t2.actual_sqlcode THEN'CONSISTENT'ELSE'*** BEHAVIOR CHANGED ***'ENDAS 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 targetORDERBYCASEWHEN t1.actual_sqlcode != t2.actual_sqlcode THEN0ELSE1END,
t1.test_name;
Upgrade Testing Is Not Optional
Test every error handling path on the target version before production cutover — the Oracle pre-upgrade utility does not test application PL/SQL behavior
Compare actual SQLCODE values between source and target versions — any difference is a behavioral change that requires investigation
Include implicit conversion tests with trailing spaces, leading spaces, empty strings, and NLS-specific decimal separators — these are the most common breaking changes on 23ai
Include JSON function tests with malformed inputs — 23ai returns more detailed errors that may break SQLERRM parsing
Automate the test harness so it runs as part of every patch upgrade, not just major version upgrades — Oracle has changed behavior in patch releases too
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.
● Production incidentPOST-MORTEMseverity: high
19c-to-23ai Upgrade Breaks Batch Error Handling
Symptom
Batch 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.
Assumption
The 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 cause
Oracle 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.
Fix
1. 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 upgrades
Oracle 23ai enforces stricter implicit conversion rules than 19c — this is the single most common source of post-upgrade errors
ORA-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 23ai
The Oracle pre-upgrade utility does not test application-level PL/SQL behavior — it checks database-level compatibility only
Implicit 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 23ai7 entries
Symptom · 01
New ORA-06502 (numeric or value error) after 19c-to-23ai upgrade in code that was previously error-free
→
Fix
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.
Symptom · 02
ORA-06512 stack depth changed after upgrade — stack traces have fewer or more frames than the same error on the previous version
→
Fix
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.
Symptom · 03
JSON-related errors (ORA-40441, ORA-40462, ORA-40834) appearing on 23ai that were never seen on 19c
→
Fix
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.
Symptom · 04
Boolean handling differences in PL/SQL exception blocks on 23ai — conditions that evaluated one way on 19c now evaluate differently
→
Fix
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.
Symptom · 05
SQL Firewall (23ai) blocking or logging legitimate error-raising procedures as security anomalies
→
Fix
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.
Symptom · 06
ORA-43923 (immutable table modification) errors appearing after 21c or 23ai upgrade
→
Fix
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.
Symptom · 07
DDL scripts failing with ORA-00955 (name already used) on 19c but succeeding on 23ai, or vice versa
→
Fix
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.
★ Version Upgrade Error Audit Cheat SheetCommands 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 action
Search 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 now
Add 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 action
Check 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 now
Recompile 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 action
Find 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 now
Cross-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 action
Find 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 now
Review 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.
Oracle Version Error Handling Feature Comparison
Feature
Oracle 19c
Oracle 21c
Oracle 23ai
Implicit VARCHAR2-to-NUMBER conversion
Permissive — trailing and leading whitespace silently trimmed
Same permissive behavior as 19c
Strict — trailing whitespace raises ORA-06502
ORA-06512 message format
at "OWNER.OBJECT", line N
Identical to 19c
Identical to 19c
FORMAT_ERROR_BACKTRACE depth
Full line numbers, all frames preserved
Full line numbers, all frames preserved
Full line numbers but compiler inlining may eliminate intermediate frames
JSON error reporting
Basic error messages — error code only
Enhanced with native JSON type context
Detailed: JSON path, character position, expected vs actual type
BOOLEAN in SQL
Not supported — PL/SQL only
Limited support in some contexts
Full native SQL support — changes NULL handling in SQL-context evaluations
SQL Firewall
Not available
Not available
Available — observation and blocking modes, 5–15 ms overhead per statement
Immutable tables
Not available
Available — ORA-43923 for UPDATE/DELETE
Available — same behavior as 21c
IF NOT EXISTS DDL
Not available — requires exception handler for ORA-00955
Not available
Available — eliminates ORA-00955 and ORA-00942 for guarded DDL
SQL Domains
Not available
Not available
Available — ORA-11534 for domain constraint violations (distinct from ORA-02290)
SQL Macros
Not available
Available — line numbers shift to expansion site in ORA-06512 stacks
Available — same behavior as 21c
JSON Duality Views
Not available
Available — errors include JSON path context in SQLERRM
Available — enhanced diagnostics over 21c
Qualified expressions
Available — reduces ORA-06531 initialization errors
Available
Available
PLSQL_OPTIMIZE_LEVEL impact
Stable stack traces at all levels
Optimization may inline simple calls
More aggressive inlining — stack depth may decrease at levels 2 and 3
Support status
Long-term support through April 2027
Innovation release — limited support window
Long-term support — current production target
Key takeaways
1
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
2
23ai enforces stricter implicit conversion
trailing whitespace in TO_NUMBER now raises ORA-06502 — fix with TRIM + explicit format masks on 19c before upgrading
3
JSON error reporting in 23ai includes path expression, character position, and expected-vs-actual type
update SQLERRM parsing patterns in monitoring tools
4
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
5
21c introduced immutable tables (ORA-43923) and SQL macros (line number shifts in backtraces)
distinct from ORA-02290 and requires new PRAGMA EXCEPTION_INIT declarations
7
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
8
Always test error handling paths
not just happy paths — on the target version before production cutover
Common mistakes to avoid
6 patterns
×
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 PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
What are the most significant error handling changes between Oracle 19c ...
Q02SENIOR
How would you design a regression test suite to verify error handling be...
Q03SENIOR
Why might FORMAT_ERROR_BACKTRACE return different stack depths on 23ai c...
Q04SENIOR
What is the SQL Firewall in Oracle 23ai and how does it interact with PL...
Q05JUNIOR
A colleague says they do not need to retest error handling after upgradi...
Q01 of 05SENIOR
What are the most significant error handling changes between Oracle 19c and 23ai that would affect a production upgrade?
ANSWER
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.
Q02 of 05SENIOR
How would you design a regression test suite to verify error handling behavior across Oracle versions during an upgrade?
ANSWER
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.
Q03 of 05SENIOR
Why might FORMAT_ERROR_BACKTRACE return different stack depths on 23ai compared to 19c for the same PL/SQL code?
ANSWER
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.
Q04 of 05SENIOR
What is the SQL Firewall in Oracle 23ai and how does it interact with PL/SQL error handling?
ANSWER
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.
Q05 of 05JUNIOR
A 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?
ANSWER
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.
01
What are the most significant error handling changes between Oracle 19c and 23ai that would affect a production upgrade?
SENIOR
02
How would you design a regression test suite to verify error handling behavior across Oracle versions during an upgrade?
SENIOR
03
Why might FORMAT_ERROR_BACKTRACE return different stack depths on 23ai compared to 19c for the same PL/SQL code?
SENIOR
04
What is the SQL Firewall in Oracle 23ai and how does it interact with PL/SQL error handling?
SENIOR
05
A 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?
JUNIOR
FAQ · 6 QUESTIONS
Frequently Asked Questions
01
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.
Was this helpful?
02
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.
Was this helpful?
03
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).
Was this helpful?
04
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.
Was this helpful?
05
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.
Was this helpful?
06
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.