Oracle 19c vs 21c vs 23ai: Changes in Error Handling & PL/SQL
- 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
- 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
Need to find all implicit conversion code before upgrading to 23ai
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;Need to compare error behavior between source and target Oracle versions
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;Need to audit JSON error handling code for 23ai upgrade
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;Need to identify exception handlers that may behave differently on 23ai
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;Production Incident
Production Debug GuideSymptom-to-action mapping for post-upgrade error handling issues across 19c, 21c, and 23ai
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: 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; /
- 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
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: 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; /
- 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
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: 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;
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.
-- 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; /
- 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
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.
-- 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; /
- 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
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.
-- 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;
| 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
- 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
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
- QHow would you design a regression test suite to verify error handling behavior across Oracle versions during an upgrade?SeniorReveal
- QWhy might FORMAT_ERROR_BACKTRACE return different stack depths on 23ai compared to 19c for the same PL/SQL code?Mid-levelReveal
- QWhat is the SQL Firewall in Oracle 23ai and how does it interact with PL/SQL error handling?Mid-levelReveal
- 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
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.
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.