How to Read and Understand the Oracle Error Stack (ORA-XXXX + ORA-06512)
- 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
- 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
Need to map ORA-06512 line number to source code
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';Need to find all WHEN OTHERS handlers in a package
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;Need to check recent errors in the error log table
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;Need to check if a PL/SQL object has compilation errors
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';Production Incident
Production Debug GuideFrom raw error message to root cause resolution
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.
-- 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. */
- 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
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.
-- ============================================================ -- 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;
- 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
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 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; /
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.
-- 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) */
- 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.
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.
UTL_CALL_STACK has three subprograms relevant to error handling:
BACKTRACE_DEPTH returns the number of frames in the error backtrace. BACKTRACE_LINE(frame) returns the line number at a specific frame depth. BACKTRACE_UNIT(frame) returns the object name at a specific frame depth. Frame 1 is the deepest call (where the error originated). Frame BACKTRACE_DEPTH is the shallowest call.
For the call stack (not the error backtrace), DYNAMIC_DEPTH returns the number of frames, UNIT_LINE(frame) returns the line number, and CONCATENATE_SUBPROGRAM(frame) returns the fully qualified subprogram name including the package and procedure name.
UTL_CALL_STACK is available only inside exception handlers or when called from PL/SQL. It cannot be called from SQL directly.
-- UTL_CALL_STACK demo β 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 */
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.
-- ============================================================ -- 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; /
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.
============================================================ 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
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.
-- ============================================================ -- 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
- 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
| Function | Returns | Requires Active Error? | Use Case | Critical Timing |
|---|---|---|---|---|
| DBMS_UTILITY.FORMAT_ERROR_STACK | ORA-XXXXX error codes and messages (what failed) | Yes | Primary error logging β captures the error code and message | Must be called FIRST in exception handler β any SQL overwrites it |
| DBMS_UTILITY.FORMAT_ERROR_BACKTRACE | ORA-06512 call chain with line numbers (where it failed) | Yes | Stack trace logging β captures the call path to the error | Must be called FIRST in exception handler β any SQL overwrites it |
| DBMS_UTILITY.FORMAT_CALL_STACK | Current PL/SQL call stack (how execution got here) | No | Execution tracing β works even outside exception handlers | Can be called anywhere but most useful alongside error functions |
| UTL_CALL_STACK.BACKTRACE_DEPTH / BACKTRACE_LINE / BACKTRACE_UNIT | Individual stack frame data β line and object per frame (12c+) | Yes | Programmatic stack analysis without string parsing | Must be called in exception handler β same timing rules as DBMS_UTILITY |
| UTL_CALL_STACK.DYNAMIC_DEPTH / UNIT_LINE / CONCATENATE_SUBPROGRAM | Individual call stack frame data including fully qualified subprogram name (12c+) | No | Programmatic call stack inspection including package.procedure names | Can be called anywhere in PL/SQL |
| SQLCODE | Numeric error code (e.g., -942 for ORA-00942) | Yes | Programmatic error handling β switch on error type in CASE statement | Returns +100 for NO_DATA_FOUND (not -1403). Returns 0 outside exception handler. |
| SQLERRM | Error message string for the current or specified error code | Partial β accepts optional error code parameter | User-facing error display or error code lookup | Can be called with an error code parameter to look up arbitrary messages |
π― Key Takeaways
- 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
Interview Questions on This Topic
- QWhat is ORA-06512 and how do you read an Oracle error stack?JuniorReveal
- QWhy is WHEN OTHERS considered dangerous in PL/SQL and when is it appropriate?Mid-levelReveal
- QExplain the difference between FORMAT_ERROR_STACK, FORMAT_ERROR_BACKTRACE, and FORMAT_CALL_STACK.Mid-levelReveal
- QHow do you debug an ORA-01403 error in a production PL/SQL batch job?Mid-levelReveal
- QHow does RAISE_APPLICATION_ERROR's keep_errors parameter affect the error stack, and when would you use TRUE vs FALSE?SeniorReveal
- QWhat is UTL_CALL_STACK and how does it improve on DBMS_UTILITY error functions?SeniorReveal
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.
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.