ORA-06512 is a stack trace line — it shows WHERE the error occurred, not WHAT went wrong
The actual error is the ORA-XXXXX code that appears before the ORA-06512 chain begins
Read the stack bottom-up: the deepest ORA-06512 is where the error originated, the topmost is where it was reported
Line numbers reference the compiled PL/SQL object — query ALL_SOURCE to map them to code
In Oracle 12c+ use UTL_CALL_STACK for programmatic stack frame access instead of parsing DBMS_UTILITY strings
Plain-English First
An Oracle error stack is a breadcrumb trail showing exactly where your code broke and how execution got there. ORA-06512 is not the error itself — it is a pointer telling you which line of which procedure failed. The real error sits at the top of the stack, before the chain of ORA-06512 lines begins. Reading it correctly — bottom-up, starting from the deepest call — cuts debugging time from hours to minutes.
Oracle error stacks appear when a PL/SQL exception propagates through nested procedure calls. The stack contains the original error code followed by ORA-06512 entries that trace the call chain from the deepest failure point to the outermost caller.
Most developers misread the stack. They see ORA-06512 and treat it as the error to fix. It is not — it is a stack frame. The actual error is always the ORA-XXXXX code that appears before the ORA-06512 chain begins.
The ORA-06512 line numbers reference the compiled PL/SQL unit. To map a line number to source code, query ALL_SOURCE or DBA_SOURCE with the object name and line number. Without this step, the line number is meaningless.
This guide covers the complete error stack reading workflow: anatomy of the stack, line number mapping, error capture functions (including UTL_CALL_STACK for Oracle 12c+), WHEN OTHERS anti-patterns, autonomous transaction logging, how error stacks appear through application drivers (JDBC, Python, Node.js), and a production incident analysis workflow. Every code example is runnable against Oracle 19c, 21c, or 23ai.
Anatomy of an Oracle Error Stack
An Oracle error stack has three components: the originating error, the call chain trace, and optionally a user-defined error message.
The originating error is the ORA-XXXXX code that appears before any ORA-06512 lines. This is the actual failure — no data found (ORA-01403), unique constraint violated (ORA-00001), table does not exist (ORA-00942), numeric overflow (ORA-01438), or any other Oracle error code.
The call chain is a series of ORA-06512 lines. Each line identifies the PL/SQL object name (in double quotes with schema prefix) and the line number within that object where the exception was propagated. The stack is ordered from outermost caller at the top to deepest call at the bottom.
User-defined errors raised via RAISE_APPLICATION_ERROR appear as ORA-20000 through ORA-20999. These replace the originating error code and carry a custom message. The ORA-06512 chain still traces the call path.
The critical reading rule: read bottom-up. The bottom of the stack is where the error originated. The top is where it was finally reported to the caller. Fix the bottom first.
error_stack_demo.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
-- Demonstrates a nested call producing a full error stack-- Run this in SQL*Plus, SQLcl, or any Oracle client-- Tested on Oracle 19c, 21c, and 23aiCREATEORREPLACEPACKAGE stack_demo_pkg ASPROCEDURE outer_caller;
PROCEDURE middle_caller;
PROCEDURE inner_caller;
END stack_demo_pkg;
/
CREATEORREPLACEPACKAGEBODY stack_demo_pkg ASPROCEDURE inner_caller IS
v_dummy VARCHAR2(1);
BEGIN-- This raises ORA-01403: no data found-- SELECT INTO with no aggregate on a query returning zero rows-- COUNT(*) would NOT raise this error — it always returns one rowSELECT dummy INTO v_dummy
FROM dual
WHERE1 = 0;
END inner_caller;
PROCEDURE middle_caller ISBEGIN
inner_caller; -- Calls inner_caller at this lineEND middle_caller;
PROCEDURE outer_caller ISBEGIN
middle_caller; -- Calls middle_caller at this lineEND outer_caller;
END stack_demo_pkg;
/
-- Execute and observe the error stackBEGIN
stack_demo_pkg.outer_caller;
END;
/
/*
Expected error stack (read BOTTOM-UP):
ORA-01403: no data found <- REALERROR: what failed
ORA-06512: at "DEMO.STACK_DEMO_PKG", line 8 <- inner_caller: SELECTINTOORA-06512: at "DEMO.STACK_DEMO_PKG", line 16 <- middle_caller: called inner
ORA-06512: at "DEMO.STACK_DEMO_PKG", line 21 <- outer_caller: called middle
ORA-06512: at line 2 <- anonymous block
Reading order:
1. ORA-01403 is the actual error — no data found from a SELECTINTO2. Line8 (inner_caller) is WHERE it failed — investigate here first
3. Line16 (middle_caller) is the intermediate caller
4. Line21 (outer_caller) is the outermost PL/SQL caller
5. "at line 2" is the anonymous block that started execution
Common mistake: investigating line 21 first because it appears at the top.
Correct approach: investigate line 8 first because it is the deepest call.
*/
Error Stack Reading Model
Bottom of stack = deepest call = where the error actually occurred — investigate here first
Top of stack = shallowest call = where the error was finally reported to the caller
The ORA-XXXXX code before the ORA-06512 chain is the actual error — ORA-06512 is a pointer, not an error
Each ORA-06512 line maps to a specific line in a specific compiled PL/SQL object
User-defined errors (ORA-20000 through ORA-20999) replace the originating error code but the ORA-06512 chain remains intact
SELECT INTO with an aggregate like COUNT(*) never raises ORA-01403 — it always returns exactly one row with the count value
Production Insight
Developers who read the stack top-down fix the wrapper procedure instead of the root cause. The error reoccurs at a different line number on the next execution because the root cause was never addressed. The bottom-up reading rule is the single most important debugging skill in Oracle PL/SQL.
Key Takeaway
The error stack is a call trace, not a list of independent errors. ORA-06512 is a stack frame pointer — the real error is the ORA-XXXXX code before the chain. Read bottom-up and fix the deepest call first.
Mapping Line Numbers to Source Code
ORA-06512 reports line numbers relative to the compiled PL/SQL object. Package specifications and package bodies are separate objects with independent line numbering — line 1 of the package body is the first line of CREATE OR REPLACE PACKAGE BODY, not a continuation from the spec.
To find the actual code at a reported line number, query ALL_SOURCE (objects accessible to the current user), DBA_SOURCE (all objects, requires DBA role), or USER_SOURCE (objects owned by the current user).
Query a range of lines around the reported number — typically 3 lines above and 3 below — to see the full statement context. Many SQL statements span multiple lines, so the exact reported line may be a continuation, not the start of the statement.
Line numbers become stale after recompilation. If a package body is recompiled between the error occurrence and your investigation, the current line numbers may not match the compiled version that was active when the error occurred. Always check the last_ddl_time of the object against the error timestamp.
For wrapped or obfuscated PL/SQL, ALL_SOURCE contains the wrapped source — not readable text. In this case, line numbers cannot be mapped without access to the original unwrapped source files. Maintain a deployment artifact repository that stores the unwrapped source alongside each production deployment.
retrieve_error_source.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
-- ============================================================-- Step 1: Retrieve source code at the ORA-06512 line number-- Replace OWNER, PKG_NAME, and line numbers with values from your error stack-- ============================================================SELECT
line,
text
FROM all_source
WHERE owner = 'RECON' -- Schema from ORA-06512 (before the dot)AND name = 'LEDGER_PKG' -- Object from ORA-06512 (after the dot)AND type = 'PACKAGEBODY' -- Always PACKAGE BODY for package proceduresAND line BETWEEN49AND55-- Line 52 +/- 3 for contextORDERBY line;
/*
Example output:
LINETEXT---- ------------------------------------------------49PROCEDUREget_ledger_balance(p_account_id INNUMBER) IS50 v_balance NUMBER;
51BEGIN52SELECT balance INTO v_balance <- ORA-06512 points here
53FROM ledger_entries_v
54WHERE account_id = p_account_id;
55 ...
Now you know: the SELECTINTO at line 52 against ledger_entries_v
is returning zero rows for the given account_id.
*/
-- ============================================================-- Step 2: Check when the object was last compiled-- If last_ddl_time > error timestamp, line numbers may be stale-- ============================================================SELECT
object_name,
object_type,
status,
last_ddl_time,
created
FROM all_objects
WHERE object_name = 'LEDGER_PKG'AND object_type = 'PACKAGE BODY';
/*
If last_ddl_time is AFTER the error timestamp:
- The object was recompiled since the error occurred
- The line numbers in the error stack reference the OLD compilation
- Reproduce the error to get a fresh stack with current line numbers
*/
-- ============================================================-- Step 3: Retrieve source for ALL ORA-06512 lines in one query-- Useful when the stack has 3+ frames-- ============================================================SELECT
s.name AS object_name,
s.line,
TRIM(s.text) AS source_line
FROM all_source s
WHERE s.type = 'PACKAGE BODY'AND (
(s.name = 'RECON_BATCH_PKG'AND s.line BETWEEN845AND849)
OR (s.name = 'LEDGER_PKG'AND s.line BETWEEN201AND205)
OR (s.name = 'LEDGER_PKG'AND s.line BETWEEN50AND54)
)
ORDERBYCASE s.name WHEN 'LEDGER_PKG' THEN1ELSE2END, -- Deepest first
s.line;
-- ============================================================-- Step 4: Find all WHEN OTHERS handlers in the call chain-- ============================================================SELECT
name,
line,
TRIM(text) AS handler_code
FROM all_source
WHERE name IN ('RECON_BATCH_PKG', 'LEDGER_PKG')
AND type = 'PACKAGE BODY'ANDUPPER(text) LIKE'%WHEN OTHERS%'ORDERBY name, line;
Line Number Accuracy Rules
Package specs and package bodies have independent line numbering — line 1 of the body is the first line of CREATE OR REPLACE PACKAGE BODY
Always query a range of lines (reported line minus 3 to plus 3) for context — multi-line SQL statements mean the reported line may not be the start of the statement
Check last_ddl_time before investigating — if the object was recompiled after the error, line numbers reference the current compilation, not the one active during the error
Wrapped PL/SQL has no readable source in ALL_SOURCE — maintain a deployment artifact repository with unwrapped source files
Production Insight
Recompilation during incident response corrupts your debug context. If a hotfix deploy runs between the error and your investigation, every line number in the original stack is now stale. Capture the full error stack text immediately during incident response — before any code changes — and verify compilation timestamps before mapping lines to source.
Key Takeaway
ORA-06512 line numbers require ALL_SOURCE lookup to become actionable. Specs and bodies have independent line numbering. Recompilation invalidates line mappings. Always check last_ddl_time against the error timestamp before trusting the line numbers.
WHEN OTHERS and Error Stack Corruption
WHEN OTHERS is the most dangerous exception handler in PL/SQL. When used without re-raising the exception or logging the full error context, it silently swallows the error and corrupts the error stack for all upstream callers. The calling procedure receives no indication that a failure occurred.
The correct use of WHEN OTHERS requires one of two patterns. Pattern 1: log the full error context and re-raise with RAISE to preserve the original error. Pattern 2: log the full error context and raise a custom error with RAISE_APPLICATION_ERROR, passing TRUE as the third parameter to preserve the original stack.
Any other pattern — WHEN OTHERS THEN NULL, WHEN OTHERS THEN RETURN, or WHEN OTHERS with a default value return — silently hides failures. Downstream systems receive corrupted or incomplete data with no indication that an error occurred. Production systems running for years with WHEN OTHERS THEN NULL accumulate silent data corruption that is expensive to detect and recover.
Error logging must use PRAGMA AUTONOMOUS_TRANSACTION to ensure the log entry is committed independently of the failing transaction. Without autonomous transactions, the error log INSERT is committed or rolled back with the main transaction — meaning a rollback erases the error evidence.
DBMS_UTILITY.FORMAT_ERROR_STACK and FORMAT_ERROR_BACKTRACE must be captured into local variables as the FIRST operation in the exception handler. Oracle overwrites the error context on the next SQL or PL/SQL statement. Executing any INSERT, UPDATE, or COMMIT before capturing the error returns an empty or incorrect stack.
error_logging_pkg.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
-- ============================================================-- Error log table-- ============================================================CREATETABLEapp_error_log (
log_id NUMBERGENERATEDALWAYSASIDENTITYPRIMARYKEY,
module VARCHAR2(200) NOTNULL,
error_code NUMBER,
error_stack VARCHAR2(4000),
backtrace VARCHAR2(4000),
call_stack VARCHAR2(4000),
context_info VARCHAR2(4000),
created_at TIMESTAMPWITHTIMEZONEDEFAULTSYSTIMESTAMPNOTNULL
);
CREATEINDEX idx_error_log_created ONapp_error_log(created_at DESC);
CREATEINDEX idx_error_log_module ONapp_error_log(module);
-- ============================================================-- Error logging package with AUTONOMOUS_TRANSACTION-- ============================================================CREATEORREPLACEPACKAGE error_handler_pkg AS-- Log the full error context and re-raise the original exceptionPROCEDURElog_and_reraise(
p_module INVARCHAR2,
p_context_info INVARCHAR2DEFAULTNULL
);
-- Log the full error context and raise a custom error with business contextPROCEDURElog_and_raise_custom(
p_module INVARCHAR2,
p_message INVARCHAR2,
p_errno INNUMBERDEFAULT -20000
);
END error_handler_pkg;
/
CREATEORREPLACEPACKAGEBODY error_handler_pkg AS-- Private: autonomous transaction logging-- Commits the log entry independently of the caller's transaction-- If the caller rolls back, the error log entry is preservedPROCEDUREwrite_log(
p_module INVARCHAR2,
p_error_code INNUMBER,
p_error_stack INVARCHAR2,
p_backtrace INVARCHAR2,
p_call_stack INVARCHAR2,
p_context_info INVARCHAR2
) ISPRAGMA AUTONOMOUS_TRANSACTION;
BEGININSERTINTOapp_error_log (
module,
error_code,
error_stack,
backtrace,
call_stack,
context_info
) VALUES (
p_module,
p_error_code,
p_error_stack,
p_backtrace,
p_call_stack,
p_context_info
);
COMMIT; -- Commits only this autonomous transactionEND write_log;
PROCEDURElog_and_reraise(
p_module INVARCHAR2,
p_context_info INVARCHAR2DEFAULTNULL
) IS-- Capture ALL error context FIRST — before any other SQL
v_error_code NUMBER := SQLCODE;
v_error_stack VARCHAR2(4000) := DBMS_UTILITY.FORMAT_ERROR_STACK;
v_backtrace VARCHAR2(4000) := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
v_call_stack VARCHAR2(4000) := DBMS_UTILITY.FORMAT_CALL_STACK;
BEGINwrite_log(
p_module => p_module,
p_error_code => v_error_code,
p_error_stack => v_error_stack,
p_backtrace => v_backtrace,
p_call_stack => v_call_stack,
p_context_info => p_context_info
);
-- Re-raise the original exception — preserves the original error codeRAISE;
END log_and_reraise;
PROCEDURElog_and_raise_custom(
p_module INVARCHAR2,
p_message INVARCHAR2,
p_errno INNUMBERDEFAULT -20000
) IS
v_error_code NUMBER := SQLCODE;
v_error_stack VARCHAR2(4000) := DBMS_UTILITY.FORMAT_ERROR_STACK;
v_backtrace VARCHAR2(4000) := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
v_call_stack VARCHAR2(4000) := DBMS_UTILITY.FORMAT_CALL_STACK;
BEGINwrite_log(
p_module => p_module,
p_error_code => v_error_code,
p_error_stack => v_error_stack,
p_backtrace => v_backtrace,
p_call_stack => v_call_stack,
p_context_info => p_message
);
-- TRUE as third parameter preserves the original error chain
RAISE_APPLICATION_ERROR(
p_errno,
p_message || ' | Original: ' || SUBSTR(v_error_stack, 1, 1000),
TRUE-- keep_errors: preserves the original error stack
);
END log_and_raise_custom;
END error_handler_pkg;
/
-- ============================================================-- Usage in application code-- ============================================================CREATEORREPLACEPROCEDUREcharge_account(
p_account_id INNUMBER,
p_amount INNUMBER
) IS
v_balance NUMBER;
BEGINSELECT balance INTO v_balance
FROM accounts
WHERE account_id = p_account_id;
IF v_balance < p_amount THEN
RAISE_APPLICATION_ERROR(-20001, 'Insufficient balance for account ' || p_account_id);
ENDIF;
UPDATE accounts
SET balance = balance - p_amount
WHERE account_id = p_account_id;
EXCEPTIONWHEN NO_DATA_FOUND THEN-- Specific handler: log with business context and raise custom error
error_handler_pkg.log_and_raise_custom(
p_module => 'charge_account',
p_message => 'Account not found: ' || p_account_id,
p_errno => -20002
);
WHENOTHERSTHEN-- Generic handler: log everything and re-raise the original error
error_handler_pkg.log_and_reraise(
p_module => 'charge_account',
p_context_info => 'account_id=' || p_account_id || ', amount=' || p_amount
);
END charge_account;
/
WHEN OTHERS Anti-Patterns That Cause Production Data Corruption
WHEN OTHERS THEN NULL — silently swallows the error; downstream systems receive no failure indication and process corrupted data
WHEN OTHERS THEN RETURN — returns control to the caller with no error; the caller assumes success
WHEN OTHERS with INSERT before FORMAT_ERROR_STACK — the INSERT overwrites the error context; the log captures the wrong error or an empty stack
WHEN OTHERS without PRAGMA AUTONOMOUS_TRANSACTION in the log procedure — if the main transaction rolls back, the error log entry is also rolled back and the error evidence is lost
Production Insight
Run this query against every production schema periodically: SELECT name, line, TRIM(text) FROM all_source WHERE type = 'PACKAGE BODY' AND UPPER(text) LIKE '%WHEN OTHERS%' AND name IN (SELECT name FROM all_source WHERE type = 'PACKAGE BODY' AND UPPER(text) LIKE '%THEN NULL%'). Every result is a potential source of silent data corruption.
Key Takeaway
WHEN OTHERS without RAISE or FORMAT_ERROR_STACK logging is the most common source of silent production failures in Oracle. Error logging must use PRAGMA AUTONOMOUS_TRANSACTION to survive transaction rollbacks. Capture FORMAT_ERROR_STACK as the FIRST operation in every exception handler — any prior SQL overwrites the error context.
DBMS_UTILITY Error Capture Functions
Oracle provides three functions in DBMS_UTILITY for capturing error context. Each answers a different question and must be called in the correct order.
FORMAT_ERROR_STACK returns the ORA-XXXXX error codes and messages. It answers: what failed? This is the primary function for error logging — it captures the error code and message that caused the exception.
FORMAT_ERROR_BACKTRACE returns the ORA-06512 call chain with line numbers. It answers: where did it fail? This provides the stack trace showing the path from the deepest failure to the outermost handler.
FORMAT_CALL_STACK returns the current PL/SQL call stack at the point where it is invoked. It answers: how did execution get here? Unlike the other two, it works outside exception handlers and is useful for tracing execution flow during debugging.
All three functions return VARCHAR2 strings. In Oracle 12c+, for deeply nested call chains, the strings can exceed 4000 bytes. Use the CLOB-returning overloads or truncate defensively when storing in VARCHAR2 columns.
Critical timing rule: capture all three functions into local variables as the FIRST operation in the exception handler. Oracle clears the error context when the next SQL or PL/SQL statement executes. An INSERT, COMMIT, or even a simple assignment from another function call can overwrite the error stack.
error_capture_demo.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
-- Demonstrates correct order and content of each error capture functionCREATEORREPLACEPROCEDURE error_capture_demo IS
v_dummy VARCHAR2(1);
BEGIN-- Force an error: query a non-existent tableEXECUTEIMMEDIATE'SELECT dummy FROM non_existent_table_xyz'INTO v_dummy;
EXCEPTIONWHENOTHERSTHEN-- STEP 1: Capture ALL error context into variables FIRST-- Do NOT execute any SQL before these assignmentsDECLARE
v_sqlcode NUMBER := SQLCODE;
v_sqlerrm VARCHAR2(4000) := SQLERRM;
v_error_stack VARCHAR2(4000) := DBMS_UTILITY.FORMAT_ERROR_STACK;
v_backtrace VARCHAR2(4000) := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
v_call_stack VARCHAR2(4000) := DBMS_UTILITY.FORMAT_CALL_STACK;
BEGIN-- STEP 2: Now safe to output or log
DBMS_OUTPUT.PUT_LINE('=== SQLCODE ===');
DBMS_OUTPUT.PUT_LINE(v_sqlcode);
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('=== SQLERRM ===');
DBMS_OUTPUT.PUT_LINE(v_sqlerrm);
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('=== FORMAT_ERROR_STACK (What failed?) ===');
DBMS_OUTPUT.PUT_LINE(v_error_stack);
DBMS_OUTPUT.PUT_LINE('=== FORMAT_ERROR_BACKTRACE (Where did it fail?) ===');
DBMS_OUTPUT.PUT_LINE(v_backtrace);
DBMS_OUTPUT.PUT_LINE('=== FORMAT_CALL_STACK (How did we get here?) ===');
DBMS_OUTPUT.PUT_LINE(v_call_stack);
-- STEP 3: Re-raise the original errorRAISE;
END;
END error_capture_demo;
/
-- Run itSETSERVEROUTPUTONBEGIN
error_capture_demo;
END;
/
/*
Expected output:
=== SQLCODE ===
-942
=== SQLERRM ===
ORA-00942: table or view does not exist
=== FORMAT_ERROR_STACK (What failed?) ===
ORA-00942: table or view does not exist
=== FORMAT_ERROR_BACKTRACE (Where did it fail?) ===
ORA-06512: at "DEMO.ERROR_CAPTURE_DEMO", line 5
=== FORMAT_CALL_STACK (How did we get here?) ===
----- PL/SQL Call Stack -----
object line object
handle number name
0x... 12 procedure DEMO.ERROR_CAPTURE_DEMO
0x... 2 anonymous block
Note:
- FORMAT_ERROR_STACK returns the error code and message
- FORMAT_ERROR_BACKTRACE returns the ORA-06512 call chain
- FORMAT_CALL_STACK returns the full call stack including non-error frames
- SQLCODE returns the numeric error code (note: +100 for NO_DATA_FOUND)
*/
Error Capture Function Selection
FORMAT_ERROR_STACK — What failed? Returns ORA-XXXXX codes and messages. Primary function for error logging.
FORMAT_ERROR_BACKTRACE — Where did it fail? Returns ORA-06512 call chain with line numbers. Primary function for stack trace analysis.
FORMAT_CALL_STACK — How did execution get here? Returns the full call stack. Works even outside exception handlers. Use for execution tracing.
SQLCODE — Returns the numeric error code. Note: returns +100 for NO_DATA_FOUND (not -1403). Returns 0 when called outside an exception handler.
SQLERRM — Returns the error message string. Accepts an optional error code parameter for looking up arbitrary error messages.
Capture ALL of these into local variables as the FIRST operation — Oracle clears error context on the next SQL statement.
Production Insight
The most common error logging mistake is executing the INSERT into the error log table before capturing FORMAT_ERROR_STACK. The INSERT succeeds, but the error stack variable contains the result of the INSERT operation — not the original error. The error log shows an empty or incorrect stack. Fix: assign all FORMAT functions to local variables before any SQL.
Key Takeaway
Three functions, three questions: what failed (FORMAT_ERROR_STACK), where failed (FORMAT_ERROR_BACKTRACE), how did we get here (FORMAT_CALL_STACK). Capture all three as the FIRST operation in the exception handler. SQLCODE returns +100 for NO_DATA_FOUND, not -1403.
Oracle 12c introduced UTL_CALL_STACK, which provides programmatic access to individual stack frames instead of the formatted strings returned by DBMS_UTILITY. For any Oracle version 12c or later — including 19c, 21c, and 23ai — UTL_CALL_STACK is the recommended approach for production error handling that needs to parse or analyze the stack programmatically.
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE returns a single formatted string that must be parsed with INSTR and SUBSTR to extract object names and line numbers. UTL_CALL_STACK provides direct access to each frame's depth, line number, owner, and object name through function calls — no string parsing required.
UTL_CALL_STACK has three subprograms relevant to error handling:
BACKTRACE_DEPTH returns the number of frames in the error backtrace. BACKTRACE_LINE(frame) returns the line number at a specific frame depth. BACKTRACE_UNIT(frame) returns the object name at a specific frame depth. Frame 1 is the deepest call (where the error originated). Frame BACKTRACE_DEPTH is the shallowest call.
For the call stack (not the error backtrace), DYNAMIC_DEPTH returns the number of frames, UNIT_LINE(frame) returns the line number, and CONCATENATE_SUBPROGRAM(frame) returns the fully qualified subprogram name including the package and procedure name.
UTL_CALL_STACK is available only inside exception handlers or when called from PL/SQL. It cannot be called from SQL directly.
utl_call_stack_demo.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
-- UTL_CALL_STACK demo — programmatic stack frame access-- Requires Oracle 12c or later (19c, 21c, 23ai)CREATEORREPLACEPACKAGE utl_stack_demo_pkg ASPROCEDURE outer_proc;
PROCEDURE inner_proc;
END utl_stack_demo_pkg;
/
CREATEORREPLACEPACKAGEBODY utl_stack_demo_pkg ASPROCEDURE inner_proc IS
v_dummy VARCHAR2(1);
BEGIN-- Force ORA-01403SELECT dummy INTO v_dummy FROM dual WHERE1 = 0;
END inner_proc;
PROCEDURE outer_proc ISBEGIN
inner_proc;
EXCEPTIONWHENOTHERSTHEN-- ===== UTL_CALL_STACK: Error Backtrace =====-- Iterate over backtrace frames programmatically-- No string parsing required
DBMS_OUTPUT.PUT_LINE('=== Error Backtrace (UTL_CALL_STACK) ===');
DBMS_OUTPUT.PUT_LINE('Backtrace depth: ' || UTL_CALL_STACK.BACKTRACE_DEPTH);
DBMS_OUTPUT.PUT_LINE('');
FOR i IN1 .. UTL_CALL_STACK.BACKTRACE_DEPTH LOOP
DBMS_OUTPUT.PUT_LINE(
'Frame ' || i || ': ' ||
'Line ' || UTL_CALL_STACK.BACKTRACE_LINE(i) || ' of ' ||
NVL(UTL_CALL_STACK.BACKTRACE_UNIT(i), 'anonymous block')
);
ENDLOOP;
DBMS_OUTPUT.PUT_LINE('');
-- ===== UTL_CALL_STACK: Current Call Stack =====
DBMS_OUTPUT.PUT_LINE('=== Current Call Stack (UTL_CALL_STACK) ===');
DBMS_OUTPUT.PUT_LINE('Dynamic depth: ' || UTL_CALL_STACK.DYNAMIC_DEPTH);
DBMS_OUTPUT.PUT_LINE('');
FOR i IN1 .. UTL_CALL_STACK.DYNAMIC_DEPTH LOOPBEGIN
DBMS_OUTPUT.PUT_LINE(
'Frame ' || i || ': ' ||
'Line ' || UTL_CALL_STACK.UNIT_LINE(i) || ' of ' ||
NVL(UTL_CALL_STACK.CONCATENATE_SUBPROGRAM(i), 'anonymous block')
);
EXCEPTIONWHENOTHERSTHEN-- Some frames may not have unit information
DBMS_OUTPUT.PUT_LINE(
'Frame ' || i || ': ' ||
'Line ' || UTL_CALL_STACK.UNIT_LINE(i) || ' (no unit info)'
);
END;
ENDLOOP;
RAISE;
END outer_proc;
END utl_stack_demo_pkg;
/
SETSERVEROUTPUTONBEGIN
utl_stack_demo_pkg.outer_proc;
END;
/
/*
Expected output:
=== ErrorBacktrace (UTL_CALL_STACK) ===
Backtrace depth: 2Frame1: Line7 of DEMO.UTL_STACK_DEMO_PKG <- inner_proc (deepest)
Frame2: Line13 of DEMO.UTL_STACK_DEMO_PKG <- outer_proc (shallowest)
=== CurrentCallStack (UTL_CALL_STACK) ===
Dynamic depth: 3Frame1: Line18 of DEMO.UTL_STACK_DEMO_PKG.OUTER_PROC
Frame2: Line2 of anonymous block
Frame3: ... (Oracle internal frames)
Key differences from DBMS_UTILITY:
- Individual frame access via function calls — no string parsing
- BACKTRACE_UNIT returns the object name directly
- CONCATENATE_SUBPROGRAM returns the fully qualified name
including package and procedure (e.g., PKG_NAME.PROC_NAME)
- Frame1 is the deepest call, Frame N is the shallowest
*/
When to Use UTL_CALL_STACK vs DBMS_UTILITY
Use DBMS_UTILITY.FORMAT_ERROR_STACK and FORMAT_ERROR_BACKTRACE when you need the full error context as a string for logging. Use UTL_CALL_STACK when you need to programmatically inspect individual frames — for example, to build structured error metadata, to extract the specific failing procedure name without string parsing, or to implement frame-level error enrichment. Both approaches require Oracle 12c or later. For Oracle 11g and earlier, DBMS_UTILITY is the only option.
Production Insight
UTL_CALL_STACK eliminates the fragile string parsing that teams build around DBMS_UTILITY.FORMAT_ERROR_BACKTRACE. Parsing DBMS_UTILITY output breaks when Oracle changes the format string between versions. UTL_CALL_STACK returns discrete values per frame — no parsing needed, no format dependency.
Key Takeaway
UTL_CALL_STACK (Oracle 12c+) provides programmatic access to individual stack frames without string parsing. Frame 1 is the deepest call. BACKTRACE_LINE and BACKTRACE_UNIT replace the need to parse FORMAT_ERROR_BACKTRACE output. Use DBMS_UTILITY for string-based logging and UTL_CALL_STACK for programmatic stack analysis.
Custom Error Codes with RAISE_APPLICATION_ERROR
RAISE_APPLICATION_ERROR raises user-defined errors with codes in the range -20000 to -20999 and a custom message of up to 2048 bytes. Custom errors replace the originating ORA-XXXXX in the error stack but preserve the ORA-06512 call chain.
The third parameter controls stack preservation. When TRUE (referred to as keep_errors in Oracle documentation), the original error chain is preserved — the custom error is appended to the existing stack. When FALSE (the default), the existing stack is replaced — only the custom error and its ORA-06512 chain are visible. Use TRUE when re-raising a caught exception with additional business context. Use FALSE when raising a new, unrelated error.
Design a consistent error code scheme so that calling code can handle different failure modes programmatically. Map specific code ranges to specific categories: -20001 to -20009 for validation errors, -20010 to -20019 for not-found errors, -20020 to -20029 for authorization errors, -20100 and above for system errors. Document the scheme and enforce it across teams.
Never wrap a known Oracle error in a generic RAISE_APPLICATION_ERROR with a vague message. Wrapping ORA-01403 in ORA-20000 with the message 'An error occurred' loses the semantic meaning that calling code and monitoring systems depend on. Preserve the original error code in the custom message or use the keep_errors parameter to preserve the stack.
error_codes_pkg.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
-- ============================================================-- Centralized error code constants and helper procedures-- ============================================================CREATEORREPLACEPACKAGE app_error_pkg AS-- Error code ranges — documented and enforced
c_validation_err CONSTANTNUMBER := -20001;
c_not_found_err CONSTANTNUMBER := -20010;
c_auth_err CONSTANTNUMBER := -20020;
c_business_rule CONSTANTNUMBER := -20030;
c_system_err CONSTANTNUMBER := -20100;
-- Raise a validation error with field contextPROCEDUREraise_validation(
p_field INVARCHAR2,
p_message INVARCHAR2
);
-- Raise a not-found error with entity contextPROCEDUREraise_not_found(
p_entity INVARCHAR2,
p_id INVARCHAR2
);
-- Re-raise a caught exception with business context-- Preserves the original error chain (keep_errors = TRUE)PROCEDUREreraise_with_context(
p_context INVARCHAR2
);
END app_error_pkg;
/
CREATEORREPLACEPACKAGEBODY app_error_pkg ASPROCEDUREraise_validation(
p_field INVARCHAR2,
p_message INVARCHAR2
) ISBEGIN
RAISE_APPLICATION_ERROR(
c_validation_err,
'Validation failed [' || p_field || ']: ' || p_message
);
-- keep_errors defaults to FALSE — this is a new error, not a re-raiseEND raise_validation;
PROCEDUREraise_not_found(
p_entity INVARCHAR2,
p_id INVARCHAR2
) ISBEGIN
RAISE_APPLICATION_ERROR(
c_not_found_err,
p_entity || ' not found: ' || p_id
);
END raise_not_found;
PROCEDUREreraise_with_context(
p_context INVARCHAR2
) IS
v_original VARCHAR2(4000) := DBMS_UTILITY.FORMAT_ERROR_STACK;
BEGIN-- TRUE = keep_errors: preserves the original error chain in the stack-- The caller sees both the custom message and the original ORA-XXXXX
RAISE_APPLICATION_ERROR(
c_system_err,
p_context || ' | Original: ' || SUBSTR(v_original, 1, 800),
TRUE-- Preserve original error stack
);
END reraise_with_context;
END app_error_pkg;
/
-- ============================================================-- Usage: calling code handles errors by code-- ============================================================BEGINcharge_account(99999, 100);
EXCEPTIONWHENOTHERSTHENCASESQLCODEWHEN app_error_pkg.c_not_found_err THEN
DBMS_OUTPUT.PUT_LINE('Account not found — routing to creation flow');
WHEN app_error_pkg.c_validation_err THEN
DBMS_OUTPUT.PUT_LINE('Validation error: ' || SQLERRM);
WHEN app_error_pkg.c_business_rule THEN
DBMS_OUTPUT.PUT_LINE('Business rule violation: ' || SQLERRM);
ELSE
DBMS_OUTPUT.PUT_LINE('Unexpected error: ' || SQLERRM);
RAISE; -- Re-raise unknown errors — do not swallowENDCASE;
END;
/
RAISE_APPLICATION_ERROR keep_errors Parameter
The third parameter of RAISE_APPLICATION_ERROR is called keep_errors in Oracle documentation. When TRUE, the original error chain is preserved — the custom error is added to the existing stack. When FALSE (default), the existing stack is replaced. Use TRUE when adding business context to a caught exception. Use FALSE when raising a new error unrelated to a previously caught exception. Choosing FALSE when re-raising discards the root cause forever.
Production Insight
If every error in your system raises ORA-20000 with a generic message, your monitoring and alerting systems cannot distinguish between validation failures, not-found errors, and system crashes. A consistent error code scheme enables automated triage — each code range maps to a different severity level and response procedure.
Key Takeaway
RAISE_APPLICATION_ERROR with keep_errors => TRUE preserves the original error chain when re-raising. Consistent error code ranges (-20001 to -20009 for validation, -20010 to -20019 for not-found) enable programmatic error handling. If every error uses -20000, you have no error handling — you have error hiding.
Error Stacks Through Application Drivers
Most developers encounter Oracle error stacks not in SQL*Plus or SQLcl but through an application layer — Java/JDBC, Python, or Node.js. Each driver wraps the Oracle error stack in its own exception type. Understanding how each driver surfaces the stack prevents misreading the error at the application level.
In all drivers, the full ORA-XXXXX + ORA-06512 chain is preserved in the error message string. The reading rules are identical regardless of the driver: find the ORA-XXXXX code, read the ORA-06512 chain bottom-up, map line numbers to source via ALL_SOURCE.
The driver-specific differences are in how the error is structured — the error code property name, the message format, and whether the stack is split across multiple exception properties or concatenated into a single string.
For structured logging in production, parse the ORA-XXXXX code from the error message and include it as a separate field in log entries. This enables filtering error logs by Oracle error code across all application layers.
driver_error_formats.txtTEXT
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
============================================================
JAVA / JDBC
============================================================
try {
callableStatement.execute();
} catch (SQLException e) {
// Error code: numeric ORA code without the ORA- prefix
int oracleErrorCode = e.getErrorCode(); // e.g., 1403
// Full message: includes ORA-XXXXX + ORA-06512 chain
String message = e.getMessage();
// "ORA-01403: no data found\nORA-06512: at \"RECON.LEDGER_PKG\", line 52\n..."
// SQL state: Oracle-specific state code
String sqlState = e.getSQLState(); // e.g., "42000"
// For chained exceptions (rare in OracleJDBC):
SQLException next = e.getNextException();
}
============================================================
PYTHON / python-oracledb (cx_Oracle successor)
============================================================
import oracledb
try:
cursor.callproc('charge_account', [99999, 100])
except oracledb.DatabaseError as e:
error_obj = e.args[0]
# Error code: numeric ORA code
error_code = error_obj.code # e.g., 1403
# Full message: includes ORA-XXXXX + ORA-06512 chain
error_message = error_obj.message
# 'ORA-01403: no data found\nORA-06512: at "RECON.LEDGER_PKG", line 52\n...'
# Offset (forSQL parse errors): character position of the error
error_offset = error_obj.offset # e.g., 0
# For structured logging:
logger.error(
'Oracle error',
extra={
'ora_code': error_code,
'ora_message': error_message,
'procedure': 'charge_account',
'params': {'account_id': 99999, 'amount': 100}
}
)
============================================================
NODE.JS / node-oracledb
============================================================
const oracledb = require('oracledb');
try {
await connection.execute('BEGIN charge_account(:id, :amt); END;',
{ id: 99999, amt: 100 });
} catch (err) {
// Error code: numeric ORA code
const errorCode = err.errorNum; // e.g., 1403
// Full message: includes ORA-XXXXX + ORA-06512 chain
const message = err.message;
// 'ORA-01403: no data found\nORA-06512: at "RECON.LEDGER_PKG", line 52\n...'
// Offset (forSQL parse errors)
const offset = err.offset; // e.g., 0
// For structured logging:
console.error(JSON.stringify({
oraCode: errorCode,
oraMessage: message,
procedure: 'charge_account',
params: { accountId: 99999, amount: 100 }
}));
}
============================================================
Key points across all drivers:
============================================================
1. The full ORA-XXXXX + ORA-06512 chain is in the message string
2. The numeric error code is available as a separate property
3. SQLCODE +100 (NO_DATA_FOUND) maps to error code 1403 in drivers
4. ORA-20000 to ORA-20999 (RAISE_APPLICATION_ERROR) appear with
the custom message text as the driver's error message
5. ParseORA-XXXXX from the message for structured logging —
include it as a separate field to enable filtering by error code
Structured Error Logging Across Application Layers
Parse the ORA-XXXXX code from the driver error message and include it as a separate field in your application log entries. This enables querying error logs by Oracle error code across Java, Python, and Node.js services without parsing log message strings. Example: {"oraCode": 1403, "oraMessage": "...", "service": "billing", "procedure": "charge_account"}.
Production Insight
Application developers who are unfamiliar with Oracle error stacks see the JDBC or Python exception and treat the entire message as an opaque string. Training the team to extract the ORA-XXXXX code and read the ORA-06512 chain — regardless of the application layer — is the single highest-leverage debugging skill investment for teams that work with Oracle.
Key Takeaway
Oracle error stacks are preserved intact through all application drivers. The reading rules are identical regardless of whether you see the stack in SQL*Plus, Java, Python, or Node.js. Parse the ORA-XXXXX code from the driver error message for structured logging. The error code property name differs by driver but the Oracle error content is the same.
Production Error Stack Analysis Workflow
During an incident, the error stack is your primary diagnostic tool. This five-step workflow converts a raw error stack into actionable debugging steps.
Step 1: Identify the real error. Scan the stack for the ORA-XXXXX code before any ORA-06512 lines. This is the root cause error. Look it up in Oracle error documentation if unfamiliar. Common examples: ORA-01403 (no data found), ORA-00001 (unique constraint violated), ORA-01438 (numeric value exceeds precision), ORA-04091 (mutating table in trigger).
Step 2: Read the stack bottom-up. Start at the deepest ORA-06512 line. This is the call site where the error originated. Map the line number to source code using ALL_SOURCE.
Step 3: Examine the source at the failing line. Query ALL_SOURCE for a range around the reported line. Identify the specific SQL statement or PL/SQL operation. The line may be in the middle of a multi-line SQL statement — look above for the statement start.
Step 4: Trace the data. Once you know what failed and where, determine why the data caused the failure. Query the tables referenced in the failing statement with the same predicate. Check for missing rows, constraint violations, unexpected NULL values, or data type mismatches.
Step 5: Check for WHEN OTHERS corruption. If the error code is generic (ORA-06512 as the only code, or ORA-20000 with a vague message), search the call chain for WHEN OTHERS handlers. One of them may have swallowed the original error and re-raised a generic custom error.
incident_analysis.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
-- ============================================================-- PRODUCTION INCIDENT ANALYSIS WORKFLOW-- Run these queries in order during an active incident-- ============================================================-- Step 1: Find recent errors in the application error logSELECT
log_id,
module,
error_code,
SUBSTR(error_stack, 1, 200) AS error_preview,
created_at
FROM app_error_log
WHERE created_at > SYSTIMESTAMP - INTERVAL'1'HOURORDERBY created_at DESCFETCHFIRST20ROWSONLY;
-- Step 2: Get the full error stack for a specific log entrySELECT
error_stack,
backtrace,
call_stack,
context_info
FROM app_error_log
WHERE log_id = :log_id_from_step_1;
-- Step 3: Map ALL ORA-06512 line numbers to source code-- Update the object names and line numbers from the backtraceSELECT
s.name AS object_name,
s.line,
TRIM(s.text) AS source_line
FROM all_source s
WHERE s.type = 'PACKAGE BODY'AND (
(s.name = 'RECON_BATCH_PKG'AND s.line BETWEEN845AND849)
OR (s.name = 'LEDGER_PKG'AND s.line BETWEEN201AND205)
OR (s.name = 'LEDGER_PKG'AND s.line BETWEEN50AND54)
)
ORDERBYCASE s.name
WHEN'LEDGER_PKG'THEN1WHEN'RECON_BATCH_PKG'THEN2ELSE3END,
s.line;
-- Step 4: Verify compilation timestamps-- If last_ddl_time > error timestamp, line numbers are staleSELECT
object_name,
object_type,
status,
last_ddl_time
FROM all_objects
WHERE object_name IN ('RECON_BATCH_PKG', 'LEDGER_PKG')
AND object_type = 'PACKAGE BODY'ORDERBY object_name;
-- Step 5: Find all WHEN OTHERS handlers in the call chainSELECT
name,
line,
TRIM(text) AS handler_code
FROM all_source
WHERE name IN ('RECON_BATCH_PKG', 'LEDGER_PKG')
AND type = 'PACKAGE BODY'ANDUPPER(text) LIKE'%WHEN OTHERS%'ORDERBY name, line;
-- Step 6: Trace the data at the failing SELECT INTO-- If ORA-01403 on ledger_entries_v:SELECT account_type, COUNT(*) AS entry_count
FROM ledger_entries_v
GROUPBY account_type
ORDERBY account_type;
-- Compare with the full accounts tableSELECT account_type, COUNT(*) AS account_count
FROM accounts
GROUPBY account_type
ORDERBY account_type;
-- Missing account_type in the view is likely the root cause-- Fix the view filter, then add a pre-flight validation check
Incident Response Error Stack Checklist
Capture the full error stack text immediately — before any hotfix deploys that could recompile objects and change line numbers
Identify the ORA-XXXXX code — this determines the failure category and narrows the investigation
Map the deepest ORA-06512 to source code — this is your starting point, not the topmost ORA-06512
Check last_ddl_time for all objects in the stack — if any were recompiled after the error, line numbers are stale
Search for WHEN OTHERS handlers in the call chain — verify none are swallowing the real error
Query the tables referenced at the failing line to trace the data that caused the failure
Production Insight
Incident response speed correlates directly with error stack reading skill. Engineers who read the stack bottom-up and query ALL_SOURCE immediately resolve incidents in minutes. Engineers who read top-down and investigate the wrapper procedure spend hours. Train your team on this workflow before the next incident — it is the highest-ROI debugging skill in Oracle development.
Key Takeaway
Five steps from error stack to root cause: identify the real ORA-XXXXX error, read bottom-up, map to source via ALL_SOURCE, trace the data at the failing statement, check for WHEN OTHERS corruption. This workflow converts a raw stack into actionable steps in under five minutes.
● Production incidentPOST-MORTEMseverity: high
Nightly Batch Job Misdiagnosed for 9 Days Due to Top-Down Stack Reading
Symptom
The nightly batch job logged ORA-06512 at line 847 of RECON_BATCH_PKG. The engineer reviewed line 847, found a SELECT INTO statement, and added NO_DATA_FOUND handling. The job failed the next night with the same ORA-01403 error at a different line number. This pattern repeated for 9 days — each fix addressed a wrapper line, never the root cause.
Assumption
The engineer assumed ORA-06512 was the error to fix. They treated each ORA-06512 line number as an independent failure point instead of recognizing the chain as a single call trace leading to one root cause.
Root cause
The full error stack showed:
ORA-01403: no data found
ORA-06512: at "RECON.RECON_BATCH_PKG", line 847
ORA-06512: at "RECON.LEDGER_PKG", line 203
ORA-06512: at "RECON.LEDGER_PKG", line 52
Read bottom-up: the root cause was at line 52 of LEDGER_PKG — a SELECT INTO on a view that excluded a new account type added 9 days earlier. Line 203 was the calling procedure within LEDGER_PKG. Line 847 of RECON_BATCH_PKG was the outermost caller that reported the error. The engineer had been adding exception handling at lines 847 and above, but the actual failure was at line 52.
Fix
Read the error stack bottom-up. Identified ORA-01403 as the real error. Queried ALL_SOURCE for line 52 of LEDGER_PKG — found a SELECT INTO against a view. Queried the view — confirmed the new account type was excluded from the view filter. Added the new account type to the view. Added a pre-flight validation step in the batch job that checks all active account types exist in the view before processing begins. Added autonomous transaction error logging to capture the full stack on first failure.
Key lesson
Always read the error stack bottom-up — the deepest ORA-06512 is where the error originated, not where it was reported
ORA-06512 is a stack frame pointer, not an error code — fix the ORA-XXXXX that precedes the chain
Adding exception handling at the wrapper level masks the root cause and causes the error to resurface at different line numbers on each execution
Pre-flight data validation catches configuration issues before they become runtime exceptions in batch jobs
Production debug guideFrom raw error message to root cause resolution7 entries
Symptom · 01
Error message shows only ORA-06512 with no preceding ORA-XXXXX code
→
Fix
The exception was raised with RAISE_APPLICATION_ERROR in the -20000 to -20999 range. The custom error code replaces the originating ORA-XXXXX. Check the procedure at the deepest ORA-06512 line for RAISE_APPLICATION_ERROR calls and read the error message text for context.
Symptom · 02
ORA-06512 points to a line number but the source code at that line does not match the expected operation
→
Fix
The PL/SQL object was recompiled after the error occurred. Line numbers reference the currently compiled version, not the version active during the error. Check: SELECT last_ddl_time FROM all_objects WHERE object_name = 'OBJECT_NAME'. If last_ddl_time is after the error timestamp, reproduce the error to get a fresh stack with current line numbers.
Symptom · 03
Multiple ORA-06512 lines — unsure which one to investigate
→
Fix
Always start from the LAST ORA-06512 in the stack — this is the deepest call where the error originated. Work upward through the call chain. The first ORA-06512 is the outermost caller. Fix the deepest call first.
Symptom · 04
Error log shows empty error stack or wrong error code
→
Fix
The exception handler executed SQL before capturing FORMAT_ERROR_STACK. Oracle overwrites the error context on the next SQL operation. Fix: capture FORMAT_ERROR_STACK and FORMAT_ERROR_BACKTRACE into local variables as the FIRST statements in the handler, before any INSERT, UPDATE, COMMIT, or other SQL.
Symptom · 05
WHEN OTHERS handler is swallowing the original error — downstream systems receive no error indication
→
Fix
Search for WHEN OTHERS in the call chain: SELECT line, text FROM all_source WHERE name = 'PKG_NAME' AND type = 'PACKAGE BODY' AND UPPER(text) LIKE '%WHEN OTHERS%'. Verify every match either calls RAISE or logs FORMAT_ERROR_STACK before raising a custom error. Replace any WHEN OTHERS THEN NULL with proper error handling.
Symptom · 06
Error stack from a trigger does not clearly show which DML statement fired it
→
Fix
Trigger error stacks include the trigger name and line number but not the triggering DML. Check the trigger definition: SELECT trigger_name, triggering_event, table_name FROM all_triggers WHERE trigger_name = 'TRIGGER_NAME'. The triggering_event and table_name tell you which DML operation caused the trigger to fire.
Symptom · 07
Error stack appears differently in the application layer (Java, Python, Node.js) than in SQL*Plus
→
Fix
Application drivers wrap Oracle error stacks in their own exception types. In JDBC, the full stack is in SQLException.getMessage(). In python-oracledb, it is in DatabaseError.args[0].message. In node-oracledb, it is in error.message. The ORA-XXXXX codes and ORA-06512 chain are preserved in the message string — parse them the same way.
★ Oracle Error Stack Quick Debug Cheat SheetFast diagnostics for Oracle PL/SQL error stack analysis. Run these queries against the database where the error occurred.
Need to map ORA-06512 line number to source code−
Immediate action
Query ALL_SOURCE for the object and line range
Commands
SELECT line, text FROM all_source WHERE name = 'PKG_NAME' AND type = 'PACKAGE BODY' AND line BETWEEN 49 AND 55 ORDER BY line;
SELECT object_name, object_type, last_ddl_time FROM all_objects WHERE object_name = 'PKG_NAME' AND object_type = 'PACKAGE BODY';
Fix now
If last_ddl_time is after the error timestamp, line numbers may be stale. Reproduce the error to get a fresh stack.
Need to find all WHEN OTHERS handlers in a package+
Immediate action
Search for WHEN OTHERS across the package body
Commands
SELECT line, TRIM(text) AS code FROM all_source WHERE name = 'PKG_NAME' AND type = 'PACKAGE BODY' AND UPPER(text) LIKE '%WHEN OTHERS%' ORDER BY line;
SELECT line, TRIM(text) AS code FROM all_source WHERE name = 'PKG_NAME' AND type = 'PACKAGE BODY' AND UPPER(text) LIKE '%THEN NULL%' ORDER BY line;
Fix now
Every WHEN OTHERS must either call RAISE or log FORMAT_ERROR_STACK. Replace WHEN OTHERS THEN NULL with proper error handling immediately.
Need to check recent errors in the error log table+
Immediate action
Query the error log for recent failures
Commands
SELECT log_id, module, SUBSTR(error_stack, 1, 200) AS error_preview, created_at FROM app_error_log WHERE created_at > SYSTIMESTAMP - INTERVAL '1' HOUR ORDER BY created_at DESC FETCH FIRST 20 ROWS ONLY;
SELECT module, COUNT(*) AS error_count FROM app_error_log WHERE created_at > SYSTIMESTAMP - INTERVAL '24' HOUR GROUP BY module ORDER BY error_count DESC;
Fix now
Identify the module with the highest error count. Read the full error_stack for the most recent occurrence. Apply the bottom-up reading workflow.
Need to check if a PL/SQL object has compilation errors+
Immediate action
Query ALL_ERRORS for the object
Commands
SELECT line, position, text FROM all_errors WHERE name = 'PKG_NAME' AND type = 'PACKAGE BODY' ORDER BY sequence;
SELECT object_name, object_type, status FROM all_objects WHERE object_name = 'PKG_NAME';
Fix now
If status is INVALID, the object failed compilation and will raise ORA-06508 at runtime. Fix compilation errors first.
Oracle Error Capture Functions Comparison
Function
Returns
Requires Active Error?
Use Case
Critical Timing
DBMS_UTILITY.FORMAT_ERROR_STACK
ORA-XXXXX error codes and messages (what failed)
Yes
Primary error logging — captures the error code and message
Must be called FIRST in exception handler — any SQL overwrites it
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
ORA-06512 call chain with line numbers (where it failed)
Yes
Stack trace logging — captures the call path to the error
Must be called FIRST in exception handler — any SQL overwrites it
DBMS_UTILITY.FORMAT_CALL_STACK
Current PL/SQL call stack (how execution got here)
No
Execution tracing — works even outside exception handlers
Can be called anywhere but most useful alongside error functions
Error message string for the current or specified error code
Partial — accepts optional error code parameter
User-facing error display or error code lookup
Can be called with an error code parameter to look up arbitrary messages
Key takeaways
1
ORA-06512 is a stack frame pointer
the real error is the ORA-XXXXX code before the chain begins
2
Always read the error stack bottom-up
the deepest ORA-06512 is where the error originated, the topmost is where it was reported
3
WHEN OTHERS without RAISE or FORMAT_ERROR_STACK logging silently swallows errors and corrupts the stack for all upstream callers
4
Capture FORMAT_ERROR_STACK as the FIRST operation in every exception handler
any prior SQL overwrites the error context
5
Error logging must use PRAGMA AUTONOMOUS_TRANSACTION
without it, a transaction rollback also rolls back the error evidence
6
Map line numbers to source via ALL_SOURCE
verify last_ddl_time before trusting line numbers after recompilation
7
Use UTL_CALL_STACK (Oracle 12c+) for programmatic stack frame access without string parsing
8
RAISE_APPLICATION_ERROR with keep_errors TRUE preserves the original error chain
FALSE discards it permanently
Common mistakes to avoid
7 patterns
×
Treating ORA-06512 as the error to fix instead of reading it as a stack frame
Symptom
Engineers add exception handling at the ORA-06512 line number without investigating the preceding ORA-XXXXX error. The error reoccurs at a different line number on the next execution because the root cause was never addressed.
Fix
Read the error stack bottom-up. Identify the ORA-XXXXX code before the ORA-06512 chain — this is the actual error. Investigate the deepest ORA-06512 line first — this is where the error originated. Fix the root cause at the deepest call site.
×
Using WHEN OTHERS without RAISE or FORMAT_ERROR_STACK logging
Symptom
Errors are silently swallowed. Downstream systems receive corrupted or incomplete data. Error logs show no record of the failure. The system appears healthy while producing incorrect results.
Fix
Every WHEN OTHERS handler must either call RAISE to propagate the original exception, or capture FORMAT_ERROR_STACK and FORMAT_ERROR_BACKTRACE before raising a custom error. Audit all PL/SQL for WHEN OTHERS THEN NULL — it is the single most dangerous anti-pattern in production Oracle.
×
Executing SQL before capturing FORMAT_ERROR_STACK in the exception handler
Symptom
Error log entries show an empty error stack or the wrong error. The INSERT or COMMIT executed before FORMAT_ERROR_STACK overwrote the error context stored by Oracle.
Fix
Capture FORMAT_ERROR_STACK, FORMAT_ERROR_BACKTRACE, and SQLCODE into local variables as the FIRST statements in every exception handler. Do not execute any SQL — including INSERT, UPDATE, COMMIT, or SELECT — before these assignments.
×
Using RAISE_APPLICATION_ERROR with keep_errors FALSE when re-raising a caught exception
Symptom
The error log shows only the custom error message. The original ORA-XXXXX error code and its call chain are permanently lost. Post-mortem debugging cannot determine the root cause.
Fix
Use TRUE as the third parameter (keep_errors) to RAISE_APPLICATION_ERROR when re-raising a caught exception with additional context. This preserves the original error chain. Use FALSE (default) only when raising a new, unrelated error.
×
Investigating error stacks after the PL/SQL object was recompiled
Symptom
Line numbers in ORA-06512 point to incorrect source code. Engineers spend hours investigating the wrong lines because the compilation has changed since the error occurred.
Fix
Check last_ddl_time before investigating: SELECT last_ddl_time FROM all_objects WHERE object_name = 'PKG_NAME' AND object_type = 'PACKAGE BODY'. If last_ddl_time is after the error timestamp, reproduce the error to get a fresh stack with current line numbers.
×
Error logging procedure does not use PRAGMA AUTONOMOUS_TRANSACTION
Symptom
Error log entries disappear after a transaction rollback. The main transaction's ROLLBACK also rolls back the error log INSERT. The error evidence is lost and the failure appears to have never happened.
Fix
Always use PRAGMA AUTONOMOUS_TRANSACTION in the error logging procedure. This commits the log entry independently of the calling transaction. If the caller rolls back, the error log entry is preserved.
×
Using COUNT(*) with SELECT INTO expecting ORA-01403 when no rows match
Symptom
The procedure runs without error when it should fail. COUNT(*) always returns exactly one row containing the count value — even when no rows match the predicate. It never raises NO_DATA_FOUND.
Fix
Use a non-aggregate SELECT INTO (e.g., SELECT column INTO variable FROM table WHERE condition) to trigger ORA-01403 on zero rows. If you need to check existence, use an IF statement after COUNT(*) rather than relying on the exception.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01JUNIOR
What is ORA-06512 and how do you read an Oracle error stack?
Q02SENIOR
Why is WHEN OTHERS considered dangerous in PL/SQL and when is it appropr...
Q03SENIOR
Explain the difference between FORMAT_ERROR_STACK, FORMAT_ERROR_BACKTRAC...
Q04SENIOR
How do you debug an ORA-01403 error in a production PL/SQL batch job?
Q05SENIOR
How does RAISE_APPLICATION_ERROR's keep_errors parameter affect the erro...
Q06SENIOR
What is UTL_CALL_STACK and how does it improve on DBMS_UTILITY error fun...
Q01 of 06JUNIOR
What is ORA-06512 and how do you read an Oracle error stack?
ANSWER
ORA-06512 is a stack trace entry, not an error itself. It indicates the PL/SQL object name and line number where an exception was propagated. To read the error stack: identify the ORA-XXXXX code before the ORA-06512 chain — this is the actual error. Then read the ORA-06512 lines bottom-up: the deepest line (last in the stack) is where the error originated, the topmost (first in the stack) is where it was finally reported. Map each line number to source code by querying ALL_SOURCE with the object name and line number. Always verify the object's last_ddl_time has not changed since the error — recompilation invalidates line numbers.
Q02 of 06SENIOR
Why is WHEN OTHERS considered dangerous in PL/SQL and when is it appropriate?
ANSWER
WHEN OTHERS without RAISE or logging silently swallows exceptions. The error does not propagate to the caller, so no upstream error handling executes. The original error code and stack trace are lost. Downstream systems may receive corrupted data because the failure was hidden. WHEN OTHERS is appropriate when it either re-raises with RAISE (preserving the original exception) or captures the full error context using DBMS_UTILITY.FORMAT_ERROR_STACK and FORMAT_ERROR_BACKTRACE before raising a user-defined error via RAISE_APPLICATION_ERROR with keep_errors = TRUE. The error logging procedure must use PRAGMA AUTONOMOUS_TRANSACTION so the log entry survives a transaction rollback. WHEN OTHERS THEN NULL is the single most dangerous line of PL/SQL in production systems.
Q03 of 06SENIOR
Explain the difference between FORMAT_ERROR_STACK, FORMAT_ERROR_BACKTRACE, and FORMAT_CALL_STACK.
ANSWER
FORMAT_ERROR_STACK returns the ORA-XXXXX error codes and messages — it answers what failed. FORMAT_ERROR_BACKTRACE returns the ORA-06512 call chain with line numbers — it answers where it failed. FORMAT_CALL_STACK returns the current PL/SQL call stack even when no error has occurred — it answers how execution reached the current point. All three must be called as the first operation in an exception handler because Oracle clears the error context when the next SQL statement executes. In Oracle 12c and later, UTL_CALL_STACK provides programmatic access to individual stack frames without the string parsing required by DBMS_UTILITY functions.
Q04 of 06SENIOR
How do you debug an ORA-01403 error in a production PL/SQL batch job?
ANSWER
ORA-01403 means no data found — a SELECT INTO returned zero rows. Read the error stack bottom-up to find the deepest ORA-06512 line. Map the line number to source code using ALL_SOURCE, verifying last_ddl_time has not changed since the error. Examine the SELECT INTO statement — identify the target table and predicate. Query the table with the same predicate to determine why no rows match. Common causes: missing configuration data (new category not added to a lookup table), view filters excluding the target row (a view predicate that does not cover a new data type), timing issues (row deleted between a lookup and a subsequent SELECT), or NULL comparisons that exclude expected rows (NULL = NULL evaluates to FALSE in SQL). Fix the data issue and add a pre-flight validation check that verifies the expected data exists before the batch run begins.
Q05 of 06SENIOR
How does RAISE_APPLICATION_ERROR's keep_errors parameter affect the error stack, and when would you use TRUE vs FALSE?
ANSWER
The third parameter of RAISE_APPLICATION_ERROR, called keep_errors in Oracle documentation, controls whether the existing error stack is preserved. With TRUE, the original error chain (the ORA-XXXXX code and all ORA-06512 lines from the caught exception) is preserved and the custom error is appended. With FALSE (the default), the existing stack is replaced — only the custom error and its new ORA-06512 chain are visible. Use TRUE when adding business context to a re-raised exception — the caller needs to see both the business message and the original Oracle error code for proper handling. Use FALSE when raising a new error unrelated to a previously caught exception. Choosing FALSE when re-raising is a common production mistake that permanently discards the root cause error from the stack.
Q06 of 06SENIOR
What is UTL_CALL_STACK and how does it improve on DBMS_UTILITY error functions?
ANSWER
UTL_CALL_STACK, introduced in Oracle 12c, provides programmatic access to individual stack frames via function calls instead of the formatted strings returned by DBMS_UTILITY. BACKTRACE_DEPTH returns the number of error backtrace frames. BACKTRACE_LINE(n) and BACKTRACE_UNIT(n) return the line number and object name at frame n. Frame 1 is the deepest call where the error originated. The key improvement: DBMS_UTILITY.FORMAT_ERROR_BACKTRACE returns a single string that must be parsed with INSTR and SUBSTR to extract frame data — a fragile approach that breaks if Oracle changes the format string between versions. UTL_CALL_STACK returns discrete values per frame with no parsing required. Additionally, CONCATENATE_SUBPROGRAM returns the fully qualified subprogram name including the package and procedure, which DBMS_UTILITY does not provide. UTL_CALL_STACK is the recommended approach for production error handling in Oracle 12c, 19c, 21c, and 23ai.
01
What is ORA-06512 and how do you read an Oracle error stack?
JUNIOR
02
Why is WHEN OTHERS considered dangerous in PL/SQL and when is it appropriate?
SENIOR
03
Explain the difference between FORMAT_ERROR_STACK, FORMAT_ERROR_BACKTRACE, and FORMAT_CALL_STACK.
SENIOR
04
How do you debug an ORA-01403 error in a production PL/SQL batch job?
SENIOR
05
How does RAISE_APPLICATION_ERROR's keep_errors parameter affect the error stack, and when would you use TRUE vs FALSE?
SENIOR
06
What is UTL_CALL_STACK and how does it improve on DBMS_UTILITY error functions?
SENIOR
FAQ · 7 QUESTIONS
Frequently Asked Questions
01
What does ORA-06512 mean?
ORA-06512 is not an error — it is a stack trace entry. It indicates the PL/SQL object name and line number where an exception was propagated or re-raised. The message format is: ORA-06512: at "SCHEMA.OBJECT_NAME", line NUMBER. It tells you WHERE the error was handled, not WHAT the error was. The actual error is the ORA-XXXXX code that appears before the ORA-06512 chain in the error stack. Multiple ORA-06512 lines form a call chain showing the path from the deepest failure to the outermost handler.
Was this helpful?
02
How do I find the source code at an ORA-06512 line number?
Query ALL_SOURCE with the object name and line number from the ORA-06512 message: SELECT text FROM all_source WHERE name = 'OBJECT_NAME' AND type = 'PACKAGE BODY' AND line BETWEEN (reported_line - 3) AND (reported_line + 3) ORDER BY line. The object name is between the double quotes in the ORA-06512 message. Query a range of lines for context because multi-line SQL statements mean the reported line may be a continuation. For wrapped PL/SQL, ALL_SOURCE contains encrypted text — you need the original unwrapped source files.
Was this helpful?
03
Should I use WHEN OTHERS in PL/SQL?
Use WHEN OTHERS only when you either re-raise with RAISE (preserving the original exception) or capture the full error context with FORMAT_ERROR_STACK and FORMAT_ERROR_BACKTRACE before raising a custom error. Never use WHEN OTHERS THEN NULL. Never use WHEN OTHERS THEN RETURN. Prefer specific exception handlers (NO_DATA_FOUND, DUP_VAL_ON_INDEX, TOO_MANY_ROWS) when the expected failure modes are known. Use WHEN OTHERS as the final catch-all after specific handlers.
Was this helpful?
04
Why does my error log show an empty error stack?
The most common cause is executing SQL before calling DBMS_UTILITY.FORMAT_ERROR_STACK. Oracle clears the error context when the next SQL or PL/SQL statement executes. If your exception handler runs an INSERT, UPDATE, COMMIT, or even a SELECT before capturing FORMAT_ERROR_STACK, the error stack is overwritten. Fix: capture FORMAT_ERROR_STACK, FORMAT_ERROR_BACKTRACE, and SQLCODE into local variables as the FIRST statements in the handler — before any other operations.
Was this helpful?
05
Do I need to upgrade to Oracle 12c to use UTL_CALL_STACK?
UTL_CALL_STACK was introduced in Oracle 12c Release 1 (12.1). It is available in all subsequent versions: 12.2, 18c, 19c, 21c, and 23ai. If you are on Oracle 11g or earlier, DBMS_UTILITY.FORMAT_ERROR_STACK, FORMAT_ERROR_BACKTRACE, and FORMAT_CALL_STACK are your only options. For Oracle 12c and later, UTL_CALL_STACK is recommended for programmatic stack analysis because it provides direct frame access without string parsing.
Was this helpful?
06
How do Oracle error stacks appear in Java, Python, and Node.js applications?
All application drivers preserve the full ORA-XXXXX + ORA-06512 chain in the error message string. In JDBC: SQLException.getErrorCode() returns the numeric code, getMessage() returns the full stack. In python-oracledb: DatabaseError.args[0].code returns the numeric code, .message returns the full stack. In node-oracledb: error.errorNum returns the numeric code, error.message returns the full stack. The reading rules are identical regardless of the application layer — find the ORA-XXXXX code, read the ORA-06512 chain bottom-up, map line numbers to source via ALL_SOURCE.
Was this helpful?
07
What happens to ORA-06512 line numbers when I recompile a package?
Line numbers in ORA-06512 reference the currently compiled version of the PL/SQL object. If you recompile a package body after an error occurred but before you investigate, the line numbers in the saved error stack now reference the old compilation and may point to different source code in the new compilation. Always check last_ddl_time before investigating: SELECT last_ddl_time FROM all_objects WHERE object_name = 'PKG_NAME' AND object_type = 'PACKAGE BODY'. If it is after the error timestamp, reproduce the error to get a fresh stack, or retrieve the pre-compilation source from your version control system.