Senior 9 min · April 14, 2026

ORA-06512 — Read Bottom-Up to Avoid 9-Day Misdiagnosis

Nightly batch misdiagnosed for 9 days because ORA-06512 was read top-down.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • 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 23ai

CREATE OR REPLACE PACKAGE stack_demo_pkg AS
  PROCEDURE outer_caller;
  PROCEDURE middle_caller;
  PROCEDURE inner_caller;
END stack_demo_pkg;
/

CREATE OR REPLACE PACKAGE BODY stack_demo_pkg AS

  PROCEDURE 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 row
    SELECT dummy INTO v_dummy
    FROM dual
    WHERE 1 = 0;
  END inner_caller;

  PROCEDURE middle_caller IS
  BEGIN
    inner_caller;  -- Calls inner_caller at this line
  END middle_caller;

  PROCEDURE outer_caller IS
  BEGIN
    middle_caller;  -- Calls middle_caller at this line
  END outer_caller;

END stack_demo_pkg;
/

-- Execute and observe the error stack
BEGIN
  stack_demo_pkg.outer_caller;
END;
/

/*
Expected error stack (read BOTTOM-UP):

ORA-01403: no data found                              <- REAL ERROR: what failed
ORA-06512: at "DEMO.STACK_DEMO_PKG", line 8          <- inner_caller: SELECT INTO
ORA-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 SELECT INTO
2. Line 8 (inner_caller) is WHERE it failed — investigate here first
3. Line 16 (middle_caller) is the intermediate caller
4. Line 21 (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  = 'PACKAGE BODY'    -- Always PACKAGE BODY for package procedures
  AND line BETWEEN 49 AND 55    -- Line 52 +/- 3 for context
ORDER BY line;

/*
Example output:
  LINE  TEXT
  ----  ------------------------------------------------
  49    PROCEDURE get_ledger_balance(p_account_id IN NUMBER) IS
  50      v_balance NUMBER;
  51    BEGIN
  52      SELECT balance INTO v_balance          <- ORA-06512 points here
  53      FROM ledger_entries_v
  54      WHERE account_id = p_account_id;
  55    ...

Now you know: the SELECT INTO 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 BETWEEN 845 AND 849)
    OR (s.name = 'LEDGER_PKG' AND s.line BETWEEN 201 AND 205)
    OR (s.name = 'LEDGER_PKG' AND s.line BETWEEN 50 AND 54)
  )
ORDER BY
  CASE s.name WHEN 'LEDGER_PKG' THEN 1 ELSE 2 END,  -- 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'
  AND UPPER(text) LIKE '%WHEN OTHERS%'
ORDER BY 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
-- ============================================================

CREATE TABLE app_error_log (
  log_id        NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  module        VARCHAR2(200)  NOT NULL,
  error_code    NUMBER,
  error_stack   VARCHAR2(4000),
  backtrace     VARCHAR2(4000),
  call_stack    VARCHAR2(4000),
  context_info  VARCHAR2(4000),
  created_at    TIMESTAMP WITH TIME ZONE DEFAULT SYSTIMESTAMP NOT NULL
);

CREATE INDEX idx_error_log_created ON app_error_log(created_at DESC);
CREATE INDEX idx_error_log_module  ON app_error_log(module);

-- ============================================================
-- Error logging package with AUTONOMOUS_TRANSACTION
-- ============================================================

CREATE OR REPLACE PACKAGE error_handler_pkg AS

  -- Log the full error context and re-raise the original exception
  PROCEDURE log_and_reraise(
    p_module       IN VARCHAR2,
    p_context_info IN VARCHAR2 DEFAULT NULL
  );

  -- Log the full error context and raise a custom error with business context
  PROCEDURE log_and_raise_custom(
    p_module  IN VARCHAR2,
    p_message IN VARCHAR2,
    p_errno   IN NUMBER DEFAULT -20000
  );

END error_handler_pkg;
/

CREATE OR REPLACE PACKAGE BODY 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 preserved
  PROCEDURE write_log(
    p_module       IN VARCHAR2,
    p_error_code   IN NUMBER,
    p_error_stack  IN VARCHAR2,
    p_backtrace    IN VARCHAR2,
    p_call_stack   IN VARCHAR2,
    p_context_info IN VARCHAR2
  ) IS
    PRAGMA AUTONOMOUS_TRANSACTION;
  BEGIN
    INSERT INTO app_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 transaction
  END write_log;

  PROCEDURE log_and_reraise(
    p_module       IN VARCHAR2,
    p_context_info IN VARCHAR2 DEFAULT NULL
  ) 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;
  BEGIN
    write_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 code
    RAISE;
  END log_and_reraise;

  PROCEDURE log_and_raise_custom(
    p_module  IN VARCHAR2,
    p_message IN VARCHAR2,
    p_errno   IN NUMBER DEFAULT -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;
  BEGIN
    write_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
-- ============================================================

CREATE OR REPLACE PROCEDURE charge_account(
  p_account_id IN NUMBER,
  p_amount     IN NUMBER
) IS
  v_balance NUMBER;
BEGIN
  SELECT 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);
  END IF;

  UPDATE accounts
  SET balance = balance - p_amount
  WHERE account_id = p_account_id;

EXCEPTION
  WHEN 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
    );

  WHEN OTHERS THEN
    -- 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 function

CREATE OR REPLACE PROCEDURE error_capture_demo IS
  v_dummy VARCHAR2(1);
BEGIN
  -- Force an error: query a non-existent table
  EXECUTE IMMEDIATE 'SELECT dummy FROM non_existent_table_xyz' INTO v_dummy;

EXCEPTION
  WHEN OTHERS THEN
    -- STEP 1: Capture ALL error context into variables FIRST
    -- Do NOT execute any SQL before these assignments
    DECLARE
      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 error
      RAISE;
    END;
END error_capture_demo;
/

-- Run it
SET SERVEROUTPUT ON
BEGIN
  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.

UTL_CALL_STACK: Programmatic Stack Access (Oracle 12c+)

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.

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)

CREATE OR REPLACE PACKAGE utl_stack_demo_pkg AS
  PROCEDURE outer_proc;
  PROCEDURE inner_proc;
END utl_stack_demo_pkg;
/

CREATE OR REPLACE PACKAGE BODY utl_stack_demo_pkg AS

  PROCEDURE inner_proc IS
    v_dummy VARCHAR2(1);
  BEGIN
    -- Force ORA-01403
    SELECT dummy INTO v_dummy FROM dual WHERE 1 = 0;
  END inner_proc;

  PROCEDURE outer_proc IS
  BEGIN
    inner_proc;
  EXCEPTION
    WHEN OTHERS THEN
      -- ===== 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 IN 1 .. 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')
        );
      END LOOP;

      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 IN 1 .. UTL_CALL_STACK.DYNAMIC_DEPTH LOOP
        BEGIN
          DBMS_OUTPUT.PUT_LINE(
            'Frame ' || i || ': ' ||
            'Line ' || UTL_CALL_STACK.UNIT_LINE(i) || ' of ' ||
            NVL(UTL_CALL_STACK.CONCATENATE_SUBPROGRAM(i), 'anonymous block')
          );
        EXCEPTION
          WHEN OTHERS THEN
            -- Some frames may not have unit information
            DBMS_OUTPUT.PUT_LINE(
              'Frame ' || i || ': ' ||
              'Line ' || UTL_CALL_STACK.UNIT_LINE(i) || ' (no unit info)'
            );
        END;
      END LOOP;

      RAISE;
  END outer_proc;

END utl_stack_demo_pkg;
/

SET SERVEROUTPUT ON
BEGIN
  utl_stack_demo_pkg.outer_proc;
END;
/

/*
Expected output:

=== Error Backtrace (UTL_CALL_STACK) ===
Backtrace depth: 2

Frame 1: Line 7 of DEMO.UTL_STACK_DEMO_PKG     <- inner_proc (deepest)
Frame 2: Line 13 of DEMO.UTL_STACK_DEMO_PKG    <- outer_proc (shallowest)

=== Current Call Stack (UTL_CALL_STACK) ===
Dynamic depth: 3

Frame 1: Line 18 of DEMO.UTL_STACK_DEMO_PKG.OUTER_PROC
Frame 2: Line 2 of anonymous block
Frame 3: ... (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)
- Frame 1 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
-- ============================================================

CREATE OR REPLACE PACKAGE app_error_pkg AS

  -- Error code ranges — documented and enforced
  c_validation_err   CONSTANT NUMBER := -20001;
  c_not_found_err    CONSTANT NUMBER := -20010;
  c_auth_err         CONSTANT NUMBER := -20020;
  c_business_rule    CONSTANT NUMBER := -20030;
  c_system_err       CONSTANT NUMBER := -20100;

  -- Raise a validation error with field context
  PROCEDURE raise_validation(
    p_field   IN VARCHAR2,
    p_message IN VARCHAR2
  );

  -- Raise a not-found error with entity context
  PROCEDURE raise_not_found(
    p_entity IN VARCHAR2,
    p_id     IN VARCHAR2
  );

  -- Re-raise a caught exception with business context
  -- Preserves the original error chain (keep_errors = TRUE)
  PROCEDURE reraise_with_context(
    p_context IN VARCHAR2
  );

END app_error_pkg;
/

CREATE OR REPLACE PACKAGE BODY app_error_pkg AS

  PROCEDURE raise_validation(
    p_field   IN VARCHAR2,
    p_message IN VARCHAR2
  ) IS
  BEGIN
    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-raise
  END raise_validation;

  PROCEDURE raise_not_found(
    p_entity IN VARCHAR2,
    p_id     IN VARCHAR2
  ) IS
  BEGIN
    RAISE_APPLICATION_ERROR(
      c_not_found_err,
      p_entity || ' not found: ' || p_id
    );
  END raise_not_found;

  PROCEDURE reraise_with_context(
    p_context IN VARCHAR2
  ) 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
-- ============================================================

BEGIN
  charge_account(99999, 100);
EXCEPTION
  WHEN OTHERS THEN
    CASE SQLCODE
      WHEN 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 swallow
    END CASE;
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 Oracle JDBC):
    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 (for SQL 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 (for SQL 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. Parse ORA-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 log

SELECT
  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' HOUR
ORDER BY created_at DESC
FETCH FIRST 20 ROWS ONLY;

-- Step 2: Get the full error stack for a specific log entry

SELECT
  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 backtrace

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 BETWEEN 845 AND 849)
    OR (s.name = 'LEDGER_PKG' AND s.line BETWEEN 201 AND 205)
    OR (s.name = 'LEDGER_PKG' AND s.line BETWEEN 50 AND 54)
  )
ORDER BY
  CASE s.name
    WHEN 'LEDGER_PKG' THEN 1
    WHEN 'RECON_BATCH_PKG' THEN 2
    ELSE 3
  END,
  s.line;

-- Step 4: Verify compilation timestamps
-- If last_ddl_time > error timestamp, line numbers are stale

SELECT
  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'
ORDER BY object_name;

-- Step 5: 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'
  AND UPPER(text) LIKE '%WHEN OTHERS%'
ORDER BY 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
GROUP BY account_type
ORDER BY account_type;

-- Compare with the full accounts table
SELECT account_type, COUNT(*) AS account_count
FROM accounts
GROUP BY account_type
ORDER BY 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
FunctionReturnsRequires Active Error?Use CaseCritical Timing
DBMS_UTILITY.FORMAT_ERROR_STACKORA-XXXXX error codes and messages (what failed)YesPrimary error logging — captures the error code and messageMust be called FIRST in exception handler — any SQL overwrites it
DBMS_UTILITY.FORMAT_ERROR_BACKTRACEORA-06512 call chain with line numbers (where it failed)YesStack trace logging — captures the call path to the errorMust be called FIRST in exception handler — any SQL overwrites it
DBMS_UTILITY.FORMAT_CALL_STACKCurrent PL/SQL call stack (how execution got here)NoExecution tracing — works even outside exception handlersCan be called anywhere but most useful alongside error functions
UTL_CALL_STACK.BACKTRACE_DEPTH / BACKTRACE_LINE / BACKTRACE_UNITIndividual stack frame data — line and object per frame (12c+)YesProgrammatic stack analysis without string parsingMust be called in exception handler — same timing rules as DBMS_UTILITY
UTL_CALL_STACK.DYNAMIC_DEPTH / UNIT_LINE / CONCATENATE_SUBPROGRAMIndividual call stack frame data including fully qualified subprogram name (12c+)NoProgrammatic call stack inspection including package.procedure namesCan be called anywhere in PL/SQL
SQLCODENumeric error code (e.g., -942 for ORA-00942)YesProgrammatic error handling — switch on error type in CASE statementReturns +100 for NO_DATA_FOUND (not -1403). Returns 0 outside exception handler.
SQLERRMError message string for the current or specified error codePartial — accepts optional error code parameterUser-facing error display or error code lookupCan 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.
FAQ · 7 QUESTIONS

Frequently Asked Questions

01
What does ORA-06512 mean?
02
How do I find the source code at an ORA-06512 line number?
03
Should I use WHEN OTHERS in PL/SQL?
04
Why does my error log show an empty error stack?
05
Do I need to upgrade to Oracle 12c to use UTL_CALL_STACK?
06
How do Oracle error stacks appear in Java, Python, and Node.js applications?
07
What happens to ORA-06512 line numbers when I recompile a package?
🔥

That's PL/SQL. Mark it forged?

9 min read · try the examples if you haven't

Previous
ORA-06512: What It Means and How to Fix It
10 / 27 · PL/SQL
Next
Best Practices for Exception Handling in Oracle PL/SQL