Skip to content
Homeβ€Ί Databaseβ€Ί How to Read and Understand the Oracle Error Stack (ORA-XXXX + ORA-06512)

How to Read and Understand the Oracle Error Stack (ORA-XXXX + ORA-06512)

Where developers are forged. Β· Structured learning Β· Free forever.
πŸ“ Part of: PL/SQL β†’ Topic 10 of 27
Learn how to read Oracle error stacks when ORA-06512 appears.
βš™οΈ Intermediate β€” basic Database knowledge assumed
In this tutorial, you'll learn
Learn how to read Oracle error stacks when ORA-06512 appears.
  • ORA-06512 is a stack frame pointer β€” the real error is the ORA-XXXXX code before the chain begins
  • Always read the error stack bottom-up β€” the deepest ORA-06512 is where the error originated, the topmost is where it was reported
  • WHEN OTHERS without RAISE or FORMAT_ERROR_STACK logging silently swallows errors and corrupts the stack for all upstream callers
✦ Plain-English analogy ✦ Real code with output ✦ Interview questions
⚑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
🚨 START HERE
Oracle Error Stack Quick Debug Cheat Sheet
Fast 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 ActionQuery 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 NowIf 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 ActionSearch 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 NowEvery 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 ActionQuery 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 NowIdentify 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 ActionQuery 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 NowIf status is INVALID, the object failed compilation and will raise ORA-06508 at runtime. Fix compilation errors first.
Production IncidentNightly Batch Job Misdiagnosed for 9 Days Due to Top-Down Stack ReadingA financial reconciliation batch job logged errors nightly. The on-call engineer added exception handling at the wrong procedure for 9 consecutive days because they read the error stack top-down instead of bottom-up.
SymptomThe 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.
AssumptionThe 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 causeThe 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.
FixRead 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 reportedORA-06512 is a stack frame pointer, not an error code β€” fix the ORA-XXXXX that precedes the chainAdding exception handling at the wrapper level masks the root cause and causes the error to resurface at different line numbers on each executionPre-flight data validation catches configuration issues before they become runtime exceptions in batch jobs
Production Debug GuideFrom raw error message to root cause resolution
Error message shows only ORA-06512 with no preceding ORA-XXXXX code→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.
ORA-06512 points to a line number but the source code at that line does not match the expected operation→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.
Multiple ORA-06512 lines — unsure which one to investigate→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.
Error log shows empty error stack or wrong error code→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.
WHEN OTHERS handler is swallowing the original error — downstream systems receive no error indication→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.
Error stack from a trigger does not clearly show which DML statement fired it→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.
Error stack appears differently in the application layer (Java, Python, Node.js) than in SQL*Plus→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 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.sql Β· SQL
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
-- 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.
*/
Mental Model
Error Stack Reading Model
The error stack is a call stack in reverse β€” the bottom is where the fire started, the top is where the smoke alarm went off.
  • 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.sql Β· SQL
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
-- ============================================================
-- 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.sql Β· SQL
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
-- ============================================================
-- 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
πŸ“Š 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.sql Β· SQL
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
-- 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)
*/
Mental Model
Error Capture Function Selection
Each function answers a different question β€” use the right one for the right purpose.
  • 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.sql Β· SQL
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
-- 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.sql Β· SQL
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
-- ============================================================
-- 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.txt Β· TEXT
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
============================================================
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.sql Β· SQL
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
-- ============================================================
-- 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.
πŸ—‚ Oracle Error Capture Functions Comparison
When to use each function, what it returns, and critical timing requirements
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

  • ORA-06512 is a stack frame pointer β€” the real error is the ORA-XXXXX code before the chain begins
  • Always read the error stack bottom-up β€” the deepest ORA-06512 is where the error originated, the topmost is where it was reported
  • WHEN OTHERS without RAISE or FORMAT_ERROR_STACK logging silently swallows errors and corrupts the stack for all upstream callers
  • Capture FORMAT_ERROR_STACK as the FIRST operation in every exception handler β€” any prior SQL overwrites the error context
  • Error logging must use PRAGMA AUTONOMOUS_TRANSACTION β€” without it, a transaction rollback also rolls back the error evidence
  • Map line numbers to source via ALL_SOURCE β€” verify last_ddl_time before trusting line numbers after recompilation
  • Use UTL_CALL_STACK (Oracle 12c+) for programmatic stack frame access without string parsing
  • RAISE_APPLICATION_ERROR with keep_errors TRUE preserves the original error chain β€” FALSE discards it permanently

⚠ Common Mistakes to Avoid

    βœ•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 Questions on This Topic

  • QWhat is ORA-06512 and how do you read an Oracle error stack?JuniorReveal
    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.
  • QWhy is WHEN OTHERS considered dangerous in PL/SQL and when is it appropriate?Mid-levelReveal
    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.
  • QExplain the difference between FORMAT_ERROR_STACK, FORMAT_ERROR_BACKTRACE, and FORMAT_CALL_STACK.Mid-levelReveal
    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.
  • QHow do you debug an ORA-01403 error in a production PL/SQL batch job?Mid-levelReveal
    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.
  • QHow does RAISE_APPLICATION_ERROR's keep_errors parameter affect the error stack, and when would you use TRUE vs FALSE?SeniorReveal
    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.
  • QWhat is UTL_CALL_STACK and how does it improve on DBMS_UTILITY error functions?SeniorReveal
    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.

Frequently Asked Questions

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.

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.

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.

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.

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.

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.

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.

πŸ”₯
Naren Founder & Author

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

← PreviousORA-06512: What It Means and How to Fix ItNext β†’Best Practices for Exception Handling in Oracle PL/SQL
Forged with πŸ”₯ at TheCodeForge.io β€” Where Developers Are Forged