Home Database ORA-06512: What It Means, Why It Appears, and How to Fix It

ORA-06512: What It Means, Why It Appears, and How to Fix It

⚡ Quick Answer
Imagine your car breaks down and a mechanic hands you a report that says 'problem started at Junction 3, travelled through Route 7, and ended at your engine.' ORA-06512 is exactly that report — it's not the actual problem, it's Oracle showing you the road the error took through your code to get to you. The real culprit is always the error listed above it. Once you understand that ORA-06512 is a breadcrumb trail, not a bug itself, the whole thing becomes a lot less scary.

If you've spent more than a week writing PL/SQL, you've seen a wall of red text that includes ORA-06512 repeated two or three times. Most developers panic, Google the error code, and end up confused because every answer says something different. That confusion happens because people treat ORA-06512 as the problem when it's actually the map to the problem — and there's a huge difference between the two.

Oracle's PL/SQL engine executes code in layers: anonymous blocks call procedures, procedures call functions, functions raise exceptions. When something goes wrong deep in that stack, Oracle doesn't just hand you the line that exploded — it hands you every layer the error passed through on the way back to you. ORA-06512 is Oracle's way of printing that call stack, one frame at a time, so you can trace the error back to its exact origin.

By the end of this article you'll be able to read an ORA-06512 stack trace like a senior DBA — identifying the root error, the exact line number that caused it, every procedure it bubbled through, and which exception-handling pattern would have caught it cleanly. You'll also know the three most common mistakes developers make when they see this error and exactly how to stop making them.

Reading the ORA-06512 Stack Trace Like a Pro

The golden rule: ORA-06512 is never the first error in the message block. Always scroll up. The actual error — ORA-01403, ORA-01400, ORA-20001, whatever it is — sits above the ORA-06512 lines. Those lines beneath it are the call stack, printed in reverse order from innermost to outermost.

Each ORA-06512 line follows this format: ORA-06512: at "SCHEMA.OBJECT_NAME", line N. That tells you the schema, the named object (procedure, function, package), and the exact line number inside that object where execution was happening when the error propagated through it. The very first ORA-06512 line after the root error is the precise location where the exception was originally raised or left unhandled.

Think of it like a stack of plates. The bottom plate is where the error started. Each plate above it is a caller that received the error and didn't catch it. Oracle prints the stack from bottom to top — innermost first, outermost last. Once you internalize that reading order, the message goes from intimidating wall of red to a precise debugging roadmap.

The line numbers Oracle reports are relative to the beginning of the stored object's source code as Oracle compiled it, not relative to any editor or file. If your package has a header section, count from line 1 of the entire package body when you're hunting the offending line.

stack_trace_demo.sql · SQL
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
-- ============================================================
-- DEMO: Producing and reading a real ORA-06512 stack trace
-- Run each block in sequence in SQL*Plus or SQL Developer
-- ============================================================

-- Step 1: Create the innermost procedure — this is where the
--         real error will actually be raised.
CREATE OR REPLACE PROCEDURE validate_employee_salary(
    p_employee_id  IN  employees.employee_id%TYPE,
    p_new_salary   IN  employees.salary%TYPE
) AS
    v_current_salary  employees.salary%TYPE;
BEGIN
    -- Fetch the existing salary; raises NO_DATA_FOUND if employee missing
    SELECT salary
    INTO   v_current_salary          -- Line 13 relative to procedure start
    FROM   employees
    WHERE  employee_id = p_employee_id;

    -- Business rule: new salary cannot be less than current salary
    IF p_new_salary < v_current_salary THEN
        -- Raise a custom application error — this becomes the ROOT error
        RAISE_APPLICATION_ERROR(
            -20001,                  -- Line 20: THIS is where the bomb drops
            'New salary ' || p_new_salary ||
            ' is below current salary ' || v_current_salary ||
            ' for employee ' || p_employee_id
        );
    END IF;
END validate_employee_salary;
/

-- Step 2: Create a middle-layer procedure that calls the inner one
--         but does NOT handle exceptions — so errors bubble upward
CREATE OR REPLACE PROCEDURE process_salary_update(
    p_employee_id  IN  employees.employee_id%TYPE,
    p_new_salary   IN  employees.salary%TYPE
) AS
BEGIN
    -- Calls inner procedure — no exception handler here, so any
    -- error from validate_employee_salary will propagate upward
    validate_employee_salary(         -- Line 9 of this procedure
        p_employee_id => p_employee_id,
        p_new_salary  => p_new_salary
    );

    -- If we get here, the salary was valid — apply the update
    UPDATE employees
    SET    salary = p_new_salary
    WHERE  employee_id = p_employee_id;

    COMMIT;
END process_salary_update;
/

-- Step 3: Anonymous block that calls the middle layer
--         Deliberately triggers the salary-drop scenario
BEGIN
    process_salary_update(
        p_employee_id => 100,   -- Assume employee 100 earns 24000
        p_new_salary  => 5000   -- This is lower — will trigger ORA-20001
    );
END;
/
▶ Output
ORA-20001: New salary 5000 is below current salary 24000 for employee 100
ORA-06512: at "HR.VALIDATE_EMPLOYEE_SALARY", line 20
ORA-06512: at "HR.PROCESS_SALARY_UPDATE", line 9
ORA-06512: at line 2

-- HOW TO READ THIS:
-- Line 1 → The ROOT cause: our custom business-rule error
-- Line 2 → Innermost named object: validate_employee_salary, line 20
-- (the RAISE_APPLICATION_ERROR call)
-- Line 3 → Middle layer: process_salary_update, line 9
-- (the call to validate_employee_salary)
-- Line 4 → The anonymous block caller, line 2
-- (the call to process_salary_update)
⚠️
Pro Tip: Start at the TOP of the error block, not ORA-06512The first ORA- code in the error output is always the root cause. Train yourself to read upward. The ORA-06512 lines are evidence, not the crime. Developers who fix ORA-06512 directly are solving the wrong problem every single time.

Why ORA-06512 Appears and the Three Exception-Handling Patterns That Prevent It Bubbling

ORA-06512 appears when an exception is raised inside a PL/SQL block and there is no EXCEPTION handler in that block to catch it. Oracle has no choice but to propagate the error upward to the caller, and as it does, it stamps each frame with an ORA-06512 line to record that the error passed through there.

There are three patterns for dealing with this, and choosing the wrong one is the most common source of debugging grief in PL/SQL codebases.

Pattern 1 — Catch and re-raise with context: You catch the exception, add useful diagnostic information (like which employee ID caused the problem), then re-raise. This preserves the full stack trace AND adds context that the original error couldn't include.

Pattern 2 — Catch and log silently: Useful for background jobs or batch processes where you don't want a single bad row to kill everything. You log the error to an error table and continue. The risk here is silent data loss if you're not disciplined about logging.

Pattern 3 — Let it propagate intentionally: Sometimes you want the caller to decide what to do with an error. This is the right choice for utility procedures. But document it explicitly so the next developer doesn't think it's an oversight.

The worst thing you can do is a bare WHEN OTHERS THEN NULL — that swallows every exception silently and makes bugs disappear without being fixed, which is a production support nightmare.

exception_handling_patterns.sql · SQL
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
-- ============================================================
-- Pattern 1: Catch, enrich with context, and re-raise
-- This gives the caller a MORE useful error than the raw one
-- ============================================================
CREATE OR REPLACE PROCEDURE apply_annual_bonus(
    p_department_id  IN  departments.department_id%TYPE,
    p_bonus_percent  IN  NUMBER
) AS
    v_employee_id   employees.employee_id%TYPE;
    v_current_salary employees.salary%TYPE;

    -- Cursor to process every employee in the department
    CURSOR c_dept_employees IS
        SELECT employee_id, salary
        FROM   employees
        WHERE  department_id = p_department_id;
BEGIN
    FOR r_employee IN c_dept_employees LOOP
        v_employee_id   := r_employee.employee_id;
        v_current_salary := r_employee.salary;

        UPDATE employees
        SET    salary = salary * (1 + p_bonus_percent / 100)
        WHERE  employee_id = v_employee_id;
    END LOOP;

    COMMIT;
EXCEPTION
    -- Pattern 1: Catch anything, add rich context, re-raise
    -- SQLERRM gives the original error message
    -- SQLCODE gives the original error number
    WHEN OTHERS THEN
        -- Roll back any partial changes from this call
        ROLLBACK;

        -- Re-raise with a new message that includes what we were doing
        -- RAISE_APPLICATION_ERROR with the original SQLERRM embedded
        RAISE_APPLICATION_ERROR(
            -20002,
            'apply_annual_bonus failed for department ' || p_department_id ||
            ' while processing employee ' || NVL(TO_CHAR(v_employee_id), 'UNKNOWN') ||
            '. Original error: ' || SQLERRM   -- Embeds the root error text
        );
END apply_annual_bonus;
/

-- ============================================================
-- Pattern 2: Catch, log to error table, continue (batch-safe)
-- ============================================================
CREATE OR REPLACE PROCEDURE batch_validate_all_salaries AS
    v_error_count  NUMBER := 0;
BEGIN
    FOR r_emp IN (SELECT employee_id, salary FROM employees) LOOP
        BEGIN
            -- Inner BEGIN..END creates a sub-block with its own handler
            -- so one failure doesn't abort the entire loop
            validate_employee_salary(
                p_employee_id => r_emp.employee_id,
                p_new_salary  => r_emp.salary
            );
        EXCEPTION
            WHEN OTHERS THEN
                v_error_count := v_error_count + 1;

                -- Log to an error tracking table instead of raising
                INSERT INTO salary_validation_errors (
                    employee_id,
                    error_code,
                    error_message,
                    error_timestamp
                ) VALUES (
                    r_emp.employee_id,
                    SQLCODE,          -- Numeric error code, e.g. -20001
                    SQLERRM,          -- Full error message text
                    SYSTIMESTAMP
                );
        END;  -- End of sub-block — loop continues to next employee
    END LOOP;

    COMMIT;
    DBMS_OUTPUT.PUT_LINE(
        'Batch complete. Errors encountered: ' || v_error_count
    );
END batch_validate_all_salaries;
/
▶ Output
-- Pattern 1 output when department 50 has a constraint issue:
ORA-20002: apply_annual_bonus failed for department 50 while processing
employee 124. Original error: ORA-02290: check constraint violated

-- Pattern 2 output after processing all employees:
Batch complete. Errors encountered: 3
-- (3 rows written to salary_validation_errors table)
⚠️
Watch Out: WHEN OTHERS THEN NULL is a career-limiting moveA bare WHEN OTHERS THEN NULL silently swallows every exception — including ones caused by bugs in your own code. Production data can be silently corrupted with no error in any log. If you need to suppress errors, at minimum log them to a table with SQLCODE, SQLERRM, and a timestamp. Your future self and your on-call colleagues will thank you.

Using DBMS_UTILITY.FORMAT_ERROR_BACKTRACE to Get the Exact Line Number

Here's a frustrating gotcha: once you catch an exception in an EXCEPTION block and then call SQLERRM, Oracle only gives you the error message — not the exact line number inside the current procedure where it originated. The ORA-06512 stack you saw before the handler caught it is gone.

This is where DBMS_UTILITY.FORMAT_ERROR_BACKTRACE becomes essential. Call it inside your EXCEPTION handler, immediately after the exception is caught, and it returns a string containing the full ORA-06512-style stack trace pointing directly to the line that raised the exception — even if multiple nested calls were involved.

The critical word there is 'immediately.' Every PL/SQL statement you execute after catching the exception can overwrite the backtrace. If you call another procedure before saving the backtrace, you'll lose the original location data. The pattern is: catch the exception, call FORMAT_ERROR_BACKTRACE as the very first thing, store it, then do whatever else you need.

Paired with DBMS_UTILITY.FORMAT_ERROR_STACK (which gives you the full error stack including all ORA- codes) and DBMS_UTILITY.FORMAT_CALL_STACK (which gives you the current live call stack even before an error), these three functions give you complete observability into what your PL/SQL code is doing at any moment.

backtrace_logging.sql · SQL
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
-- ============================================================
-- Production-grade error logging using FORMAT_ERROR_BACKTRACE
-- This is the pattern used in real enterprise Oracle systems
-- ============================================================

-- First, a table to hold our structured error logs
CREATE TABLE plsql_error_log (
    log_id           NUMBER GENERATED ALWAYS AS IDENTITY,
    procedure_name   VARCHAR2(128),
    error_code       NUMBER,
    error_message    VARCHAR2(4000),
    error_backtrace  VARCHAR2(4000),   -- The ORA-06512 trail
    error_stack      VARCHAR2(4000),   -- Full ORA- error stack
    call_stack       VARCHAR2(4000),   -- Who called whom
    logged_at        TIMESTAMP DEFAULT SYSTIMESTAMP,
    session_user     VARCHAR2(128) DEFAULT SYS_CONTEXT('USERENV','SESSION_USER')
);
/

-- A reusable logging procedure — call this from any exception handler
CREATE OR REPLACE PROCEDURE log_plsql_error(
    p_procedure_name  IN  VARCHAR2,
    p_backtrace       IN  VARCHAR2,   -- Pass in pre-captured backtrace
    p_error_stack     IN  VARCHAR2    -- Pass in pre-captured error stack
) AS
    PRAGMA AUTONOMOUS_TRANSACTION;    -- Log even if caller rolls back
BEGIN
    INSERT INTO plsql_error_log (
        procedure_name,
        error_code,
        error_message,
        error_backtrace,
        error_stack
    ) VALUES (
        p_procedure_name,
        SQLCODE,
        SQLERRM,
        p_backtrace,
        p_error_stack
    );
    COMMIT;  -- Autonomous transaction commits independently
END log_plsql_error;
/

-- The main procedure showing the capture pattern
CREATE OR REPLACE PROCEDURE recalculate_department_budget(
    p_department_id  IN  departments.department_id%TYPE
) AS
    v_total_salary    NUMBER;
    v_headcount       NUMBER;
    v_backtrace       VARCHAR2(4000);  -- Will hold our breadcrumb trail
    v_error_stack     VARCHAR2(4000);  -- Will hold the full ORA- stack
BEGIN
    SELECT SUM(salary), COUNT(*)
    INTO   v_total_salary, v_headcount
    FROM   employees
    WHERE  department_id = p_department_id;

    -- Deliberately cause a divide-by-zero if dept has no employees
    -- This raises ORA-01476: divisor is equal to zero
    UPDATE departments
    SET    budget_per_head = v_total_salary / v_headcount  -- Line 44
    WHERE  department_id   = p_department_id;

    COMMIT;
EXCEPTION
    WHEN OTHERS THEN
        -- CAPTURE THE BACKTRACE FIRST — before any other statement
        -- This preserves the exact line 44 reference in the backtrace
        v_backtrace   := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE;
        v_error_stack := DBMS_UTILITY.FORMAT_ERROR_STACK;

        -- Now safe to do other work — backtrace is saved in variables
        ROLLBACK;

        -- Log using autonomous transaction so the rollback above
        -- does not wipe out our error log entry
        log_plsql_error(
            p_procedure_name => 'RECALCULATE_DEPARTMENT_BUDGET',
            p_backtrace      => v_backtrace,
            p_error_stack    => v_error_stack
        );

        -- Re-raise so the caller knows something went wrong
        RAISE;
END recalculate_department_budget;
/

-- Test it:
BEGIN
    recalculate_department_budget(p_department_id => 999); -- No employees
END;
/

-- Check what was logged:
SELECT procedure_name,
       error_code,
       error_message,
       error_backtrace
FROM   plsql_error_log
ORDER  BY logged_at DESC
FETCH FIRST 1 ROWS ONLY;
▶ Output
-- The RAISE at the end propagates the original error to the caller:
ORA-01476: divisor is equal to zero
ORA-06512: at "HR.RECALCULATE_DEPARTMENT_BUDGET", line 44
ORA-06512: at line 2

-- What the plsql_error_log table contains:
PROCEDURE_NAME ERROR_CODE ERROR_MESSAGE
------------------------------ ----------- ------------------------------------
RECALCULATE_DEPARTMENT_BUDGET -1476 ORA-01476: divisor is equal to zero

ERROR_BACKTRACE
-----------------------------------------------------------------
ORA-06512: at "HR.RECALCULATE_DEPARTMENT_BUDGET", line 44

-- Line 44 is exactly where `v_total_salary / v_headcount` lives.
-- Even though the handler caught and re-raised, FORMAT_ERROR_BACKTRACE
-- preserved the original origin point.
🔥
Interview Gold: PRAGMA AUTONOMOUS_TRANSACTION for error loggingIf your logging procedure doesn't use PRAGMA AUTONOMOUS_TRANSACTION, a ROLLBACK in the calling procedure will delete your error log entry before it's committed — meaning you log the error and then immediately lose it. Autonomous transactions run in their own session context, so they commit independently of whatever the parent transaction does.

The ORA-06512 Patterns You'll See in Production and What Each One Means

Not all ORA-06512 stacks look the same, and experienced Oracle developers can diagnose the rough shape of a problem just by looking at the structure of the error block before reading a single line of code.

A short stack (one or two ORA-06512 lines) usually means the error was raised close to where it was called — typically a direct RAISE_APPLICATION_ERROR in a procedure called from an anonymous block. These are usually the easiest to fix.

A long stack (five or more ORA-06512 lines) is a signal that your architecture has deep call chains without exception handling at intermediate layers. The error is bubbling all the way from a low-level utility up through a business layer, a service layer, and then a top-level procedure. This isn't inherently wrong, but it does mean your debugging starting point is far from your call entry point.

A stack that includes package names with dot notation — like HR.EMPLOYEE_PKG.VALIDATE_SALARY — tells you the error passed through a packaged procedure. The line number in that frame refers to the line within the package body, which you'll need to open to investigate.

An ORA-06512 with no object name, just at line N, means the error reached an anonymous block or a top-level trigger. Anonymous blocks don't have names so Oracle can't print one — it just tells you the line within that block.

production_stack_patterns.sql · SQL
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
-- ============================================================
-- Pattern: Trigger-originated ORA-06512
-- Triggers are a common source of confusing stack traces because
-- they fire invisibly during DML — the developer doing the INSERT
-- doesn't always know a trigger exists
-- ============================================================

-- A BEFORE INSERT trigger that enforces a business rule
CREATE OR REPLACE TRIGGER enforce_salary_band
    BEFORE INSERT OR UPDATE OF salary
    ON employees
    FOR EACH ROW
DECLARE
    v_min_salary  job_grades.min_salary%TYPE;
    v_max_salary  job_grades.max_salary%TYPE;
BEGIN
    -- Look up the allowed salary range for this job
    SELECT min_salary, max_salary
    INTO   v_min_salary, v_max_salary
    FROM   job_grades
    WHERE  job_id = :NEW.job_id;   -- :NEW is the incoming row

    -- If the new salary falls outside the band, reject the DML
    IF :NEW.salary NOT BETWEEN v_min_salary AND v_max_salary THEN
        RAISE_APPLICATION_ERROR(    -- Line 21 of trigger
            -20010,
            'Salary ' || :NEW.salary ||
            ' is outside the band [' || v_min_salary ||
            ' - ' || v_max_salary || '] for job ' || :NEW.job_id
        );
    END IF;
END enforce_salary_band;
/

-- Now try to insert an out-of-band salary
INSERT INTO employees (
    employee_id, first_name, last_name,
    email, job_id, salary, department_id
) VALUES (
    207, 'Alex', 'Chen',
    'ACHEN', 'IT_PROG', 999999,  -- Way above any IT_PROG band
    60
);

-- ============================================================
-- Pattern: Package body stack trace
-- Shows how dot-notation reveals package membership
-- ============================================================
CREATE OR REPLACE PACKAGE BODY employee_pkg AS

    PROCEDURE hire_employee(
        p_first_name    IN  VARCHAR2,
        p_last_name     IN  VARCHAR2,
        p_job_id        IN  VARCHAR2,
        p_salary        IN  NUMBER,
        p_dept_id       IN  NUMBER
    ) AS
        v_new_emp_id  NUMBER;
    BEGIN
        SELECT employee_seq.NEXTVAL INTO v_new_emp_id FROM dual;

        INSERT INTO employees (
            employee_id, first_name, last_name,
            email, job_id, salary, department_id
        ) VALUES (
            v_new_emp_id,
            p_first_name, p_last_name,
            UPPER(SUBSTR(p_first_name,1,1) || p_last_name),
            p_job_id, p_salary, p_dept_id
        );                            -- The trigger fires HERE
        COMMIT;
    END hire_employee;

END employee_pkg;
/

-- Calling the package procedure — trigger fires mid-INSERT
BEGIN
    employee_pkg.hire_employee(
        p_first_name => 'Jordan',
        p_last_name  => 'Rivera',
        p_job_id     => 'IT_PROG',
        p_salary     => 999999,       -- Still out of band
        p_dept_id    => 60
    );
END;
/
▶ Output
-- Error from direct INSERT:
ORA-20010: Salary 999999 is outside the band [4000 - 10000] for job IT_PROG
ORA-06512: at "HR.ENFORCE_SALARY_BAND", line 21
ORA-04088: error during execution of trigger 'HR.ENFORCE_SALARY_BAND'

-- Error from package procedure call:
ORA-20010: Salary 999999 is outside the band [4000 - 10000] for job IT_PROG
ORA-06512: at "HR.ENFORCE_SALARY_BAND", line 21
ORA-04088: error during execution of trigger 'HR.ENFORCE_SALARY_BAND'
ORA-06512: at "HR.EMPLOYEE_PKG", line 17
ORA-06512: at line 2

-- NOTICE: ORA-04088 tells you a TRIGGER was involved.
-- The stack reads: trigger (line 21) → package body (line 17) → anonymous block
-- Whenever you see ORA-04088 in a stack, a trigger is the source.
🔥
Pro Tip: ORA-04088 is your trigger signalWhenever ORA-04088 appears alongside ORA-06512, a database trigger is involved in the error chain. This is critical to know because the developer running the DML statement may have no idea a trigger exists on that table. Search ALL_TRIGGERS for TRIGGER_NAME matching the object name in the ORA-06512 line above ORA-04088 to find it instantly.
ApproachCatches Root ErrorPreserves Line NumbersWorks After ROLLBACKBest Used For
SQLERRM in EXCEPTION blockYes — error message textNo — line info is lost once caughtYesSimple error messages in small procedures
DBMS_UTILITY.FORMAT_ERROR_BACKTRACEPartial — stack only, not messageYes — exact line of origin preservedYes if captured before ROLLBACKProduction logging, enterprise systems
DBMS_UTILITY.FORMAT_ERROR_STACKYes — full ORA- chainNo — message text only, no linesYes if captured before ROLLBACKLogging the full error code chain
Let error propagate (no handler)Yes — full stack visible to callerYes — Oracle prints full ORA-06512 trailN/A — no explicit rollback controlUtility procedures where caller decides
WHEN OTHERS THEN NULLNo — silently swallowedNo — completely lostYes (nothing to preserve)Never — this is an anti-pattern

🎯 Key Takeaways

  • ORA-06512 is a call stack reporter, not the actual error — the real error is always the ORA- code printed above it, and that is what you fix.
  • DBMS_UTILITY.FORMAT_ERROR_BACKTRACE must be the very first call in your EXCEPTION handler — every statement you execute before it risks overwriting the original line number.
  • PRAGMA AUTONOMOUS_TRANSACTION is non-negotiable in any error-logging procedure — without it, a ROLLBACK in the calling block silently erases your log entry.
  • When ORA-04088 appears in an ORA-06512 stack, a database trigger is in the chain — query ALL_TRIGGERS to find it, because the developer running the DML may not know the trigger exists.

⚠ Common Mistakes to Avoid

  • Mistake 1: Trying to fix ORA-06512 directly — Symptom: Developer searches for 'ORA-06512 fix' and doesn't find the root cause — Fix: Always find and fix the error listed ABOVE the ORA-06512 lines. ORA-06512 is a location reporter, not an error itself. The actual error code (e.g., ORA-01403, ORA-20001) is always the first line of the block.
  • Mistake 2: Calling FORMAT_ERROR_BACKTRACE too late — Symptom: The backtrace returns a different line number or NULL because another PL/SQL call executed first inside the EXCEPTION block — Fix: Assign DBMS_UTILITY.FORMAT_ERROR_BACKTRACE to a local variable as the very first executable statement inside the WHEN OTHERS THEN handler, before any INSERT, procedure call, or other statement.
  • Mistake 3: Missing PRAGMA AUTONOMOUS_TRANSACTION in the error logging procedure — Symptom: The error gets logged to the table, then the caller does ROLLBACK, and the log entry disappears — leaving you with no record of what went wrong — Fix: Add PRAGMA AUTONOMOUS_TRANSACTION to the logging procedure's declaration section. This makes it commit in its own independent database session, immune to the caller's ROLLBACK.

Interview Questions on This Topic

  • QORA-06512 keeps appearing in your logs. Walk me through exactly how you would identify the root cause and the line of code responsible — what tools and functions would you use?
  • QWhat is the difference between SQLERRM and DBMS_UTILITY.FORMAT_ERROR_BACKTRACE, and why might SQLERRM fail to give you the line number of the original error?
  • QYou have a batch PL/SQL procedure that processes 10,000 rows. One row fails mid-batch. How would you design the exception handling so the failure is logged but the remaining rows continue processing — and what happens to the error log if the entire transaction rolls back?

Frequently Asked Questions

What does ORA-06512 actually mean in Oracle PL/SQL?

ORA-06512 means 'at object_name, line N' — it's Oracle printing one frame of the PL/SQL call stack to show you the path an unhandled exception took as it bubbled up through your code. It is not an error in itself; it's a location breadcrumb. The actual error is always the ORA- code printed on the line above the first ORA-06512 entry.

Does fixing the code at the ORA-06512 line number fix the error?

Not directly — the ORA-06512 line number shows where execution was when the error passed through that frame, but the fix should target the root error printed above the ORA-06512 block. For example, if the root error is ORA-01403 (no data found), you fix the SELECT statement or add an exception handler for NO_DATA_FOUND, not the line Oracle tagged with ORA-06512.

How do I find the exact line number of an Oracle PL/SQL error after catching it in an exception handler?

Use DBMS_UTILITY.FORMAT_ERROR_BACKTRACE immediately as the first statement inside your EXCEPTION block and store the result in a VARCHAR2 variable. This function returns the ORA-06512 style stack pointing to the exact line where the exception originated, even after it has been caught. If you call any other procedure or run any SQL before capturing the backtrace, Oracle may overwrite the internal backtrace buffer.

If I have WHEN OTHERS THEN and I call RAISE to re-raise the exception, does the ORA-06512 stack change?

Yes, and this is a subtle trap. A bare RAISE inside an exception handler re-raises the original exception with its original error code and message, but Oracle resets the backtrace origin to the line of the RAISE statement itself — not the original line where the exception was first raised. To preserve the original backtrace for logging, always capture DBMS_UTILITY.FORMAT_ERROR_BACKTRACE before the RAISE, not after. The outer caller will see an updated ORA-06512 stack pointing to your RAISE line, but your log table will have the true origin.

🔥
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.

← PreviousPL/SQL Packages Explained
Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged