SQL Developer Debugging Tutorial β Catch ORA-06512 Faster
- Exception breakpoints halt execution at the exact line that raises the error β eliminating manual ORA-06512 line number parsing entirely and reducing diagnosis time from 30β90 minutes to under 5 minutes
- Both conditions must be true before debugging works: correct privilege grants AND DEBUG compilation of every procedure in the call chain β missing either produces silent failure with no error message
- The Call Stack window replaces textual ORA-06512 lines with a clickable navigable view β click any frame to jump to that source line and inspect that scope's variables
- SQL Developer's built-in PL/SQL debugger can trap exceptions at the exact line before ORA-06512 propagates up the call chain β eliminating manual stack trace parsing entirely
- Key component 1: Exception breakpoints pause execution the moment any Oracle error is raised, before exception handlers run
- Key component 2: The Call Stack window shows the full nested procedure chain triggering ORA-06512 as an interactive, clickable view β not a static text dump
- Key component 3: The Variables window reveals the exact runtime values (NULLs, truncated strings, bad numbers) that caused the error at the moment it fired
- Performance insight: remote debugging adds 200β500 ms latency per breakpoint hit versus local connections β acceptable for diagnosis, painful if you leave it on for hours
- Production insight: developers who skip debugger setup and fall back to DBMS_OUTPUT loops waste 3β5x more time on ORA-06512 triage per incident
- Biggest mistake: reading the error stack manually and correlating line numbers by hand instead of letting the debugger halt at the source β line numbers shift every time you edit the procedure, invalidating all your manual work
- Second biggest mistake: forgetting to compile with DEBUG before starting the session β breakpoints are silently ignored and the debugger appears broken
Need to enable debugging for a specific package or procedure
-- Grant required privileges (run as DBA)
GRANT DEBUG CONNECT SESSION TO your_developer_user;
GRANT DEBUG ANY PROCEDURE TO your_developer_user;
-- Alternative: grant DEBUG on specific objects only (least-privilege approach)
GRANT DEBUG ON io.thecodeforge.report.generator TO your_developer_user;
GRANT DEBUG ON io.thecodeforge.report.batch_runner TO your_developer_user;
GRANT DEBUG ON io.thecodeforge.scheduler.nightly_job TO your_developer_user;-- Compile procedures with DEBUG flag
ALTER PROCEDURE io.thecodeforge.payment.reconcile_batch COMPILE DEBUG;
ALTER PACKAGE io.thecodeforge.report.generator COMPILE DEBUG;
ALTER PACKAGE io.thecodeforge.report.generator COMPILE BODY DEBUG;
-- Verify DEBUG compilation status
SELECT name, type,
CASE WHEN debuginfo = 'YES' THEN 'DEBUG ENABLED'
ELSE 'NO DEBUG INFO β recompile with DEBUG' END AS debug_status
FROM user_procedures
WHERE name IN ('RECONCILE_BATCH', 'GENERATOR', 'BATCH_RUNNER')
ORDER BY name;Need to initiate a remote debug session from the target database
-- Run this in the TARGET database session
-- This blocks until SQL Developer attaches (or timeout expires)
BEGIN
DBMS_DEBUG_JDWP.CONNECT_TCP(
host => 'your-sqldeveloper-host.example.com', -- SQL Developer machine IP
port => 4000, -- must match SQL Developer preferences
timeout => 30 -- seconds to wait for attachment
);
-- After SQL Developer attaches, execution continues here
-- Place the failing call immediately after CONNECT_TCP
io.thecodeforge.payment.reconcile_batch(
p_batch_id => 12345,
p_run_date => DATE '2026-04-14'
);
END;
/-- In SQL Developer: Run > Debug > (select your connection)
-- SQL Developer will attach to the waiting session
-- Then set exception breakpoints in the Breakpoints window:
-- Breakpoints window > right-click > Add Exception Breakpoint
-- Error number: 1403 (for ORA-01403 NO_DATA_FOUND)
-- Error number: 1 (for ORA-00001 unique constraint violated)
-- Error number: 6502 (for ORA-06502 VALUE_ERROR)
-- Verify the debug port is open and reachable from the DB server
-- Run this from the database server OS (not SQL*Plus):
-- telnet your-sqldeveloper-host.example.com 4000Need to verify which objects in a call chain are missing DEBUG compilation
-- Check DEBUG status for all objects in a known call chain
SELECT p.object_name, p.object_type, p.procedure_name,
CASE WHEN p.debuginfo = 'YES' THEN 'DEBUG OK'
ELSE 'NEEDS: ALTER ' || p.object_type || ' ' ||
p.object_name || ' COMPILE DEBUG;' END AS action_needed
FROM user_procedures p
WHERE p.object_name IN (
'RECONCILE_BATCH', 'GENERATOR', 'BATCH_RUNNER', 'NIGHTLY_JOB'
)
ORDER BY p.object_name, p.procedure_name;-- Find all procedures that reference a specific object
-- (useful for identifying the full call chain before debugging)
SELECT DISTINCT d.name AS dependent_object, d.type AS dependent_type,
d.referenced_name AS calls, d.referenced_type
FROM user_dependencies d
WHERE d.referenced_name = 'REPORT_TEMPLATES'
AND d.referenced_type IN ('TABLE','VIEW')
ORDER BY d.name;
-- Then revert non-debug objects after the session
-- ALTER PACKAGE io.thecodeforge.report.generator COMPILE;
-- ALTER PACKAGE io.thecodeforge.report.generator COMPILE BODY;Production Incident
Production Debug GuideSymptom-to-action mapping for common debugger setup and runtime issues
ORA-06512 stack traces are informative but require manual correlation work: line numbers must be matched to source code, and that source code must be at the exact version deployed when the error occurred. Any edit between error and diagnosis shifts the line numbers. SQL Developer's debugger eliminates this entire class of friction by halting execution at the exact point where an exception is raised and presenting the full call chain as a navigable, clickable interface.
The Call Stack window is the debugger's answer to ORA-06512. Each frame in the window corresponds to one ORA-06512 line in the textual stack β except you can click on any frame to jump directly to that line in the source and inspect every variable at that scope level. The Variables window shows exactly what values were in play at the moment the error fired. Exception breakpoints mean you do not need to know in advance which line to break on β you tell the debugger 'halt on ORA-01403' and it finds the line for you.
Engineers who configure exception breakpoints before starting a debugging session consistently reduce ORA-06512 diagnosis time from 30 minutes to under 5 minutes for known error codes, and from hours to under 20 minutes for unknown error codes in deep nested call chains. This guide covers the complete setup path: privilege grants, DEBUG compilation, local and remote connection configuration, exception breakpoint configuration, Call Stack navigation, variable inspection, and the production fallback pattern for environments where interactive debugging is not available.
Privileges and DEBUG Compilation Setup
Two things must be true before SQL Developer's debugger can work: the developer must have the correct Oracle privileges, and every procedure in the call chain must be compiled with the DEBUG flag. Missing either one produces silent failures β breakpoints are ignored without error messages, and variables show as unreadable without explanation.
Privilege requirements are straightforward: DEBUG CONNECT SESSION allows the session to initiate a debug connection; DEBUG ANY PROCEDURE allows setting breakpoints in any schema's compiled objects. For least-privilege environments, you can grant DEBUG ON specific objects instead of DEBUG ANY PROCEDURE β useful when developers should only debug objects they own or have been explicitly granted access to.
DEBUG compilation embeds symbol table information into the compiled object. This is what allows the debugger to map machine instructions back to source lines and variable names. Without it, the debugger cannot resolve line numbers or variable names, so breakpoints have nothing to bind to. The compilation change is permanent until you recompile without the DEBUG flag β which you must do before promoting to production.
The DEBUG flag slightly increases object size (10β15%) due to the embedded symbol tables, but has zero effect on runtime performance when no debugger is attached. The overhead only materializes when the debugger is actively connected and a breakpoint is hit.
-- =================================================== -- Step 1: Grant debugging privileges (run as DBA) -- =================================================== -- Option A: Full debug access (appropriate for senior developers) GRANT DEBUG CONNECT SESSION TO your_developer_user; GRANT DEBUG ANY PROCEDURE TO your_developer_user; -- Option B: Least-privilege debug access (specific objects only) GRANT DEBUG CONNECT SESSION TO your_developer_user; GRANT DEBUG ON io.thecodeforge.report.generator TO your_developer_user; GRANT DEBUG ON io.thecodeforge.report.batch_runner TO your_developer_user; GRANT DEBUG ON io.thecodeforge.scheduler.nightly_job TO your_developer_user; -- Verify privileges were granted SELECT grantee, privilege, admin_option FROM dba_sys_privs WHERE grantee = 'YOUR_DEVELOPER_USER' AND privilege IN ('DEBUG CONNECT SESSION', 'DEBUG ANY PROCEDURE') ORDER BY privilege; -- =================================================== -- Step 2: Compile target objects with DEBUG flag -- Run for every object in the call chain -- =================================================== -- Compile individual procedures ALTER PROCEDURE io.thecodeforge.payment.reconcile_batch COMPILE DEBUG; ALTER PROCEDURE io.thecodeforge.scheduler.nightly_job COMPILE DEBUG; -- Compile packages: spec first, then body -- The body is where procedure code lives β the body must have DEBUG ALTER PACKAGE io.thecodeforge.report.generator COMPILE DEBUG; ALTER PACKAGE io.thecodeforge.report.generator COMPILE BODY DEBUG; ALTER PACKAGE io.thecodeforge.report.batch_runner COMPILE DEBUG; ALTER PACKAGE io.thecodeforge.report.batch_runner COMPILE BODY DEBUG; -- =================================================== -- Step 3: Verify DEBUG compilation status -- =================================================== SELECT p.object_name, p.object_type, p.procedure_name, CASE WHEN p.debuginfo = 'YES' THEN 'DEBUG ENABLED β breakpoints will work' ELSE 'DEBUG MISSING β recompile with DEBUG flag' END AS debug_status FROM user_procedures p WHERE p.object_name IN ( 'RECONCILE_BATCH', 'GENERATOR', 'BATCH_RUNNER', 'NIGHTLY_JOB' ) ORDER BY p.object_name, p.procedure_name; -- =================================================== -- Step 4: Revert to non-debug compilation -- ALWAYS run this before promoting to any environment -- beyond your local dev schema -- =================================================== ALTER PROCEDURE io.thecodeforge.payment.reconcile_batch COMPILE; ALTER PACKAGE io.thecodeforge.report.generator COMPILE; ALTER PACKAGE io.thecodeforge.report.generator COMPILE BODY; ALTER PACKAGE io.thecodeforge.report.batch_runner COMPILE; ALTER PACKAGE io.thecodeforge.report.batch_runner COMPILE BODY; ALTER PROCEDURE io.thecodeforge.scheduler.nightly_job COMPILE; -- Verify revert: all should show 'DEBUG MISSING' after revert SELECT object_name, object_type, CASE WHEN debuginfo = 'YES' THEN 'WARNING: STILL IN DEBUG MODE β DO NOT DEPLOY' ELSE 'OK: Non-debug compilation confirmed' END AS deployment_status FROM user_procedures WHERE object_name IN ( 'RECONCILE_BATCH', 'GENERATOR', 'BATCH_RUNNER', 'NIGHTLY_JOB' ) ORDER BY object_name;
- DEBUG adds symbol table entries to the compiled object: source line-to-bytecode mappings and variable name metadata
- Object size increases 10β15% due to embedded symbol tables β negligible in practice
- Runtime performance is identical when no debugger is attached β zero overhead for normal execution
- When the debugger is attached and hits a breakpoint, it uses the symbol table to map the current bytecode position back to a source line and resolve variable names
- DEBUG info is stored in the data dictionary (SYS.IDL_SB4$ and related tables), not in the SGA β it does not consume PGA or shared pool memory
- Always revert to non-debug compilation before deploying β debug objects expose internal variable names in USER_PROCEDURES and the symbol table is unnecessary overhead in production
Setting Exception Breakpoints for ORA-06512
Exception breakpoints are the highest-leverage feature in SQL Developer's debugger for ORA-06512 diagnosis. A regular line breakpoint requires you to know in advance which line to halt at. An exception breakpoint requires only that you know which error code is being raised β the debugger finds the line for you.
When an exception breakpoint fires, the debugger halts before the exception handler executes. This means you see the full pre-handler state: the exact line that raised the error, the values of all local variables, the state of any cursors, and the full Call Stack showing how execution reached that point. This is the state you need for diagnosis β not the state inside the WHEN clause after the error has already been caught.
The most common exception breakpoints for ORA-06512 investigation are ORA-01403 (NO_DATA_FOUND from SELECT INTO), ORA-00001 (unique constraint violated), ORA-06502 (VALUE_ERROR from type conversion), and ORA-01722 (invalid number from implicit conversion). If you do not know which error is causing the ORA-06512 stack, start with a general approach: set a breakpoint at the RAISE or the DML statement in the outermost procedure shown in the ORA-06512 stack, then use Step Into to drill down to the source.
-- =================================================== -- SQL Developer Exception Breakpoint Setup -- UI Path: View > Breakpoints > right-click > Add Exception Breakpoint -- =================================================== -- Common error numbers for exception breakpoints: -- Enter these in the 'Error Number' field (without ORA- prefix): -- -- 1403 β ORA-01403: NO_DATA_FOUND (SELECT INTO returned no rows) -- 1 β ORA-00001: unique constraint violated -- 1422 β ORA-01422: TOO_MANY_ROWS (SELECT INTO returned multiple rows) -- 6502 β ORA-06502: VALUE_ERROR (type conversion failure) -- 1722 β ORA-01722: invalid number (implicit conversion failure) -- 2291 β ORA-02291: FK parent key not found -- 60 β ORA-00060: deadlock detected -- 1555 β ORA-01555: snapshot too old -- =================================================== -- Execution block: run this to trigger the debug session -- The exception breakpoint will fire when the error occurs -- =================================================== DECLARE -- Declare any test variables needed v_batch_id NUMBER := 12345; v_run_date DATE := DATE '2026-04-14'; BEGIN -- For remote debugging: connect SQL Developer first, then call CONNECT_TCP -- For local debugging: skip CONNECT_TCP and just call the procedure -- Call the failing procedure β debugger halts when exception fires io.thecodeforge.payment.reconcile_batch( p_batch_id => v_batch_id, p_run_date => v_run_date ); END; / -- =================================================== -- What you see when the exception breakpoint fires: -- =================================================== -- 1. Execution halts at the EXACT line that raised ORA-01403 -- (e.g., line 142 in io.thecodeforge.report.generator) -- 2. That line is highlighted in the source editor -- 3. Variables window shows all in-scope variables: -- p_report_type = 'QUARTERLY_ADJUSTMENT' <-- the problem -- p_run_date = 2026-04-14 -- v_count = 0 <-- why NO_DATA_FOUND -- 4. Call Stack window shows: -- Frame 0: IO.THECODEFORGE.REPORT.GENERATOR.GET_TEMPLATE line 142 -- Frame 1: IO.THECODEFORGE.REPORT.BATCH_RUNNER.RUN line 87 -- Frame 2: IO.THECODEFORGE.SCHEDULER.NIGHTLY_JOB.EXECUTE line 23 -- -- Click Frame 1 -> jumps to batch_runner line 87, -- Variables window updates to show batch_runner's local variables -- Click Frame 2 -> jumps to nightly_job line 23, -- Variables window updates to show scheduler's local variables -- =================================================== -- Conditional exception breakpoint (for intermittent errors) -- Set condition in Breakpoints > Properties > Condition: -- =================================================== -- Condition expression examples (enter in Breakpoints Properties dialog): -- p_report_type = 'QUARTERLY_ADJUSTMENT' -- only fire for this report type -- p_batch_id > 10000 -- only fire for large batch IDs -- v_count = 0 -- only fire when count is zero -- p_customer_id IS NULL -- only fire for NULL customer
Navigating the Call Stack Window
The Call Stack window is the debugger's visual equivalent of the ORA-06512 textual stack trace. When an exception breakpoint fires or a regular breakpoint halts execution, the Call Stack window shows every procedure frame on the current execution stack β the same information as the ORA-06512 lines, but interactive.
Each frame in the Call Stack corresponds to one ORA-06512 line. Frame 0 is the deepest call β the procedure where execution is currently halted. The final frame is the outermost caller (often the anonymous block or the application entry point). Clicking on any frame does two things simultaneously: the source editor jumps to that line, and the Variables window updates to show the local variables at that scope level. This is the key capability β you can inspect the state of every level of the call chain, not just the frame where the error occurred.
This eliminates the most time-consuming manual debugging step: reading ORA-06512 line numbers, finding the correct source file at the correct version, counting to the right line, and then guessing what the variable values were. The debugger shows all of this without guessing.
-- =================================================== -- Textual ORA-06512 stack (what you see WITHOUT the debugger) -- =================================================== -- ORA-01403: no data found -- ORA-06512: at "IO.THECODEFORGE.REPORT.GENERATOR", line 142 -- ORA-06512: at "IO.THECODEFORGE.REPORT.BATCH_RUNNER", line 87 -- ORA-06512: at "IO.THECODEFORGE.SCHEDULER.NIGHTLY_JOB", line 23 -- ORA-06512: at line 1 -- -- Manual work required: -- 1. Open io.thecodeforge.report.generator source -- 2. Navigate to line 142 -- 3. Guess what the variable values were -- 4. Open io.thecodeforge.report.batch_runner, go to line 87 -- 5. Guess what was passed as arguments -- 6. Repeat for nightly_job line 23 -- 7. Make a change, redeploy, re-run the batch, wait 40 minutes -- Total: 2β4 hours per debugging cycle -- =================================================== -- Call Stack window (what you see WITH the debugger) -- =================================================== -- Frame 0: IO.THECODEFORGE.REPORT.GENERATOR.GET_TEMPLATE line 142 -- [highlighted in source editor β this is where execution halted] -- Frame 1: IO.THECODEFORGE.REPORT.BATCH_RUNNER.PROCESS_TYPE line 87 -- Frame 2: IO.THECODEFORGE.SCHEDULER.NIGHTLY_JOB.EXECUTE line 23 -- Frame 3: anonymous block line 5 -- -- Interactive actions: -- Click Frame 0 -> source editor shows generator line 142, -- Variables window shows: -- p_report_type = 'QUARTERLY_ADJUSTMENT' -- p_run_date = 2026-04-14 -- v_template_id = <NULL> <-- the problem -- -- Click Frame 1 -> source editor shows batch_runner line 87, -- Variables window shows: -- v_report_types = ['MONTHLY','QUARTERLY_ADJUSTMENT','WEEKLY'] -- v_current_type = 'QUARTERLY_ADJUSTMENT' -- v_batch_id = 12345 -- -- Click Frame 2 -> source editor shows nightly_job line 23, -- Variables window shows: -- v_run_date = 2026-04-14 -- v_job_name = 'NIGHTLY_REPORT_BATCH' -- -- Total: 4 minutes from breakpoint fire to root cause identified -- No redeployment, no re-execution, no guessing -- =================================================== -- Read the Call Stack correctly: bottom is the outermost caller -- =================================================== -- Frame 3 (anonymous block, line 5) β the entry point (top-level caller) -- called Frame 2 (nightly_job, line 23) -- called Frame 1 (batch_runner, line 87) -- called Frame 0 (generator, line 142) β where the error occurred -- -- This matches the ORA-06512 reading order: -- The actual error is at the TOP of the ORA-06512 text stack -- and at Frame 0 (the BOTTOM) of the Call Stack window. -- Both read: innermost call first, outermost caller last. -- =================================================== -- Stepping through the call chain manually -- (use when exception breakpoint fires at an unexpected location) -- =================================================== -- F7 (Step Into) β enter the next called procedure -- F8 (Step Over) β execute the next line without entering sub-calls -- F9 (Resume) β continue execution until the next breakpoint -- Shift+F7 (Step Out) β finish the current procedure and return to caller -- -- Recommended ORA-06512 debugging sequence: -- 1. Set exception breakpoint -- 2. Run the failing code -- 3. When breakpoint fires, inspect Frame 0 variables -- 4. Click Frame 1 to understand the calling context -- 5. Click Frame 2 to understand the entry conditions -- 6. Root cause is usually apparent within 3 frames
- Each Call Stack frame maps to one ORA-06512 line β Frame 0 is the innermost (deepest) call, matching the first ORA-06512 line after the actual error
- Clicking a frame navigates the source editor to that exact line at the exact version currently compiled β no line-number drift
- The Variables window updates to show the scope of the clicked frame β you can inspect each procedure's local variables independently
- Frame 0 is always the most valuable starting point β it is where the error occurred and where the offending variable value will be visible
- The Call Stack window does not disappear after clicking through frames β you can navigate back to Frame 0 at any time
Variable Inspection and Watch Expressions
The Variables and Watch windows are where debugging converts from 'I know where it failed' to 'I know why it failed.' After the Call Stack window shows you which line raised the error, the Variables window shows you what values were in play at that moment β the exact data state that caused the exception.
For ORA-06512 debugging, variable inspection targets the inputs to the failing operation. For a SELECT INTO that raised ORA-01403, inspect the WHERE clause parameters β one of them is likely NULL, the wrong value, or mismatched in type. For an ORA-06502 VALUE_ERROR on a string assignment, inspect the source string's length against the target variable's declared size. For ORA-01722, inspect the string being passed to TO_NUMBER β it contains a character that is not numeric.
The Watch window allows custom expressions that the Variables window does not show by default: SQLCODE, SQLERRM, cursor attributes like v_cursor%ROWCOUNT, and collection properties like v_collection.COUNT. These expressions are evaluated on every step, so keep the Watch window lean β too many expressions slow down remote debugging sessions.
When the Variables window cannot show a value (package globals are only visible when you are in that package's frame), use the Watch window with a fully qualified expression: io.thecodeforge.config_pkg.v_current_env evaluates the package global directly.
-- =================================================== -- Watch window expressions for ORA-06512 debugging -- Add these via: Watch window > right-click > Add Watch -- =================================================== -- Universal: current error state SQLCODE SQLERRM -- ORA-01403 (NO_DATA_FOUND): inspect SELECT INTO parameters p_report_type -- is this NULL or an unrecognized value? p_customer_id -- is this NULL? v_count -- is this 0 when it should be > 0? -- ORA-01422 (TOO_MANY_ROWS): confirm uniqueness assumption v_customer_count -- this should be 1, might be > 1 -- ORA-06502 (VALUE_ERROR): length and type checks LENGTH(v_string_var) -- is this longer than the target VARCHAR2? DUMP(v_string_var) -- raw bytes β reveals encoding issues LENGTH(v_description) -- compare against column definition length -- ORA-01722 (invalid number): check string content v_amount_str -- does this contain '$', ',', or spaces? REGEXP_REPLACE(v_amount_str, '[^0-9.\-]', '') -- stripped version -- ORA-00001 (unique constraint): see the duplicate key values p_order_id -- what value is being inserted as PK? p_batch_id || '_' || p_seq -- composite key components -- Cursor state inspection v_cursor%ROWCOUNT -- how many rows fetched so far? v_cursor%ISOPEN -- is the cursor still open? v_collection.COUNT -- number of elements in collection v_collection.FIRST -- first index (NULL if empty) v_collection.LAST -- last index -- Conditional expressions for context CASE WHEN p_report_type IS NULL THEN 'NULL_INPUT' ELSE p_report_type END CASE WHEN v_count = 0 THEN 'NO_TEMPLATE_FOUND' ELSE TO_CHAR(v_count) || '_FOUND' END -- =================================================== -- Programmatic variable logging for post-mortem analysis -- Use this pattern when SQL Developer debugger is unavailable -- (automated batch jobs, production debugging) -- =================================================== CREATE OR REPLACE PROCEDURE io.thecodeforge.debug.log_variables( p_procedure_name IN VARCHAR2, p_context IN CLOB ) IS PRAGMA AUTONOMOUS_TRANSACTION; BEGIN INSERT INTO io.thecodeforge.debug.variable_snapshot ( snapshot_id, procedure_name, context_dump, call_stack, session_id, db_user, created_at ) VALUES ( io.thecodeforge.debug.snapshot_seq.NEXTVAL, p_procedure_name, p_context, DBMS_UTILITY.FORMAT_CALL_STACK, -- capture the full stack at log time SYS_CONTEXT('USERENV', 'SESSIONID'), SYS_CONTEXT('USERENV', 'SESSION_USER'), SYSTIMESTAMP ); COMMIT; END log_variables; / -- Usage: add before a suspect SELECT INTO to capture pre-failure state CREATE OR REPLACE PROCEDURE io.thecodeforge.report.generator( p_report_type IN VARCHAR2, p_run_date IN DATE ) IS v_template_id NUMBER; v_count NUMBER; BEGIN -- Capture state before the suspect operation -- Remove this after debugging β it adds an autonomous transaction per call io.thecodeforge.debug.log_variables( p_procedure_name => 'GENERATOR.GET_TEMPLATE', p_context => 'p_report_type=' || NVL(p_report_type, 'NULL') || ', p_run_date=' || TO_CHAR(p_run_date, 'YYYY-MM-DD') || ', session=' || SYS_CONTEXT('USERENV', 'SESSIONID') ); -- The suspect SELECT INTO SELECT template_id INTO v_template_id FROM io.thecodeforge.report.templates WHERE report_type = p_report_type -- p_report_type = 'QUARTERLY_ADJUSTMENT' AND effective_date <= p_run_date -- and this date comparison AND active_flag = 'Y'; -- ... rest of procedure ... NULL; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20103, 'No active template found for report type: ' || NVL(p_report_type, 'NULL') || ' as of ' || TO_CHAR(p_run_date, 'YYYY-MM-DD')); END generator; /
- ORA-01403 (NO_DATA_FOUND): inspect every WHERE clause parameter β look for NULL values, type mismatches, or values that do not exist in the referenced table
- ORA-01422 (TOO_MANY_ROWS): inspect the WHERE clause β a uniqueness assumption is violated; check what makes the query return multiple rows
- ORA-06502 (VALUE_ERROR): use
LENGTH()on the source and compare against the target variable's declared size; useDUMP()to reveal encoding issues and non-printable characters - ORA-01722 (invalid number): inspect the string being converted β look for currency symbols ($, Β£), commas, spaces, or leading/trailing whitespace
- ORA-00001 (unique constraint violated): inspect the values being inserted for the constrained columns β the duplicate key is one of those values
- ORA-02291 (FK parent not found): inspect the foreign key column value β either it is NULL (NOT NULL constraint might be missing) or the parent record does not exist
DUMP() for conversion errors: it shows the raw byte content of a VARCHAR2 variable, revealing non-printable characters, unexpected encoding, or invisible trailing spaces that cause ORA-06502 and ORA-01722.DUMP() for conversion errors β invisible characters and encoding issues are only visible at the byte level.Remote Debugging for Test and Staging Environments
Remote debugging connects SQL Developer running on your workstation to a PL/SQL session running on a different database host. This is the correct approach for errors that only reproduce in test or staging environments β which is most of them, since local developer schemas rarely have production-volume data or the exact configuration that triggers the error.
The architecture is slightly counterintuitive: the database session initiates the connection outbound to SQL Developer, not the other way around. DBMS_DEBUG_JDWP.CONNECT_TCP is called from inside the database session, pointing at the SQL Developer host and port. SQL Developer listens on that port and attaches when the call is received. Once attached, both sides communicate over the JDWP (Java Debug Wire Protocol) connection β the same protocol used by Java debuggers.
The firewall implications follow from this architecture: the database server must be able to reach the SQL Developer host on the debug port (default 4000) via outbound TCP. Many corporate environments allow outbound TCP from database servers by default but restrict inbound β which makes this configuration firewall-friendly compared to alternatives where the debugger initiates the connection.
Remote debugging adds 200β500 ms latency per breakpoint interaction under normal network conditions. This is acceptable for diagnosis but becomes painful for step-by-step execution. Use Step Over (F8) rather than Step Into (F7) whenever possible to skip library code you do not need to inspect, and keep the Watch window expression count minimal to reduce per-step evaluation overhead.
-- =================================================== -- Remote debugging setup: overview of the connection flow -- =================================================== -- -- 1. SQL Developer (your workstation) starts listening on port 4000 -- Menu: Run > Remote Debug... > enter listening port > OK -- SQL Developer is now waiting for an inbound JDWP connection -- -- 2. Target database session calls CONNECT_TCP pointing at SQL Developer -- The call BLOCKS until SQL Developer attaches or timeout expires -- -- 3. SQL Developer attaches to the waiting session -- The blocked CONNECT_TCP call returns, execution continues -- -- 4. The failing procedure is called β exception breakpoints and line -- breakpoints now work as in local debugging -- -- Network requirement: -- Database server ---outbound TCP 4000---> SQL Developer workstation -- (database server initiates; SQL Developer listens) -- =================================================== -- Step 1: In SQL Developer, start the remote debug listener -- Run > Remote Debug > Listening Port: 4000 > OK -- Status bar shows: "Waiting for remote debug connection..." -- =================================================== -- =================================================== -- Step 2: In the TARGET database session, run this block -- Replace the host with your SQL Developer machine IP or hostname -- =================================================== DECLARE v_batch_id NUMBER := 12345; v_run_date DATE := DATE '2026-04-14'; BEGIN -- Initiate debug connection from database to SQL Developer -- This BLOCKS until SQL Developer attaches (or timeout expires) DBMS_DEBUG_JDWP.CONNECT_TCP( host => '10.0.1.45', -- SQL Developer host IP or hostname port => 4000, -- must match SQL Developer listening port timeout => 60 -- seconds to wait before giving up ); -- CONNECT_TCP returned β SQL Developer is now attached -- Breakpoints are now active. Call the failing procedure: io.thecodeforge.payment.reconcile_batch( p_batch_id => v_batch_id, p_run_date => v_run_date ); -- Optionally disconnect after debugging is complete -- DBMS_DEBUG_JDWP.DISCONNECT; END; / -- =================================================== -- Verify JDWP privilege (required on Oracle 12c+) -- =================================================== SELECT grantee, privilege FROM dba_sys_privs WHERE grantee = 'YOUR_USER' AND privilege LIKE '%JDWP%'; -- Grant if missing (run as DBA) GRANT JDWP TO your_developer_user; -- =================================================== -- Troubleshooting: test port reachability from DB server -- (run from OS on the database server, not from SQL*Plus) -- =================================================== -- Linux/Unix: -- nc -zv your-sqldeveloper-host.example.com 4000 -- telnet your-sqldeveloper-host.example.com 4000 -- -- Windows (PowerShell): -- Test-NetConnection -ComputerName your-sqldeveloper-host -Port 4000 -- =================================================== -- Defensive wrapper: attempt debug attach, fall back gracefully -- Useful in test automation where debugger attachment is optional -- =================================================== CREATE OR REPLACE PROCEDURE io.thecodeforge.debug.try_attach( p_debug_host IN VARCHAR2 DEFAULT 'localhost', p_debug_port IN NUMBER DEFAULT 4000, p_timeout IN NUMBER DEFAULT 15 ) IS BEGIN DBMS_DEBUG_JDWP.CONNECT_TCP( host => p_debug_host, port => p_debug_port, timeout => p_timeout ); DBMS_OUTPUT.PUT_LINE( 'Debug session attached: ' || p_debug_host || ':' || p_debug_port ); EXCEPTION WHEN OTHERS THEN -- Timeout or connection refused β continue without debugger DBMS_OUTPUT.PUT_LINE( 'Debug attach failed (' || SQLERRM || ') β running without debugger' ); END try_attach; /
Automated Stack Capture Without the Debugger
Interactive debugging is not always available. Production environments should never have SQL Developer attached β the performance impact, security exposure of DEBUG compilation, and operational risk are not justified for production debugging. Scheduled batch jobs run unattended without a developer waiting to attach a debug session. And intermittent errors that require hours of waiting for conditions to align are impractical to debug interactively.
For all of these scenarios, the fallback is programmatic stack capture using DBMS_UTILITY.FORMAT_CALL_STACK and DBMS_UTILITY.FORMAT_ERROR_STACK. These two functions together capture the same information as the debugger's Call Stack and error display β the call chain leading to the error, the exact error code and message, and the ORA-06512 frame lines. Capturing them in an AUTONOMOUS_TRANSACTION ensures the log record survives even when the main transaction rolls back due to the error.
The critical addition over basic ORA-06512 logging is the parameter capture: log the input values that were active at the time of the error alongside the stack. A stack trace without parameters tells you where the error occurred. Parameters tell you why β and enable exact reproduction in a test environment where you can attach the debugger.
-- =================================================== -- Error log table: production stack capture schema -- =================================================== CREATE TABLE io.thecodeforge.logging.error_log ( error_id NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, module_name VARCHAR2(128) NOT NULL, error_code NUMBER, error_message CLOB, call_stack CLOB, -- DBMS_UTILITY.FORMAT_CALL_STACK error_stack CLOB, -- DBMS_UTILITY.FORMAT_ERROR_STACK error_backtrace CLOB, -- DBMS_UTILITY.FORMAT_ERROR_BACKTRACE parameters CLOB, -- input values at time of error session_id NUMBER, os_user VARCHAR2(128), db_user VARCHAR2(128), client_info VARCHAR2(64), module_ctx VARCHAR2(64), action_ctx VARCHAR2(64), created_at TIMESTAMP DEFAULT SYSTIMESTAMP ); -- Index for common query patterns CREATE INDEX io.thecodeforge.logging.err_log_code_ts_idx ON io.thecodeforge.logging.error_log (error_code, created_at DESC); CREATE INDEX io.thecodeforge.logging.err_log_module_ts_idx ON io.thecodeforge.logging.error_log (module_name, created_at DESC); -- =================================================== -- Centralized error capture procedure -- Call this from EVERY exception handler before re-raising -- =================================================== CREATE OR REPLACE PROCEDURE io.thecodeforge.logging.capture_error( p_module IN VARCHAR2, p_parameters IN CLOB DEFAULT NULL ) IS PRAGMA AUTONOMOUS_TRANSACTION; -- survives main transaction rollback BEGIN INSERT INTO io.thecodeforge.logging.error_log ( module_name, error_code, error_message, call_stack, error_stack, error_backtrace, parameters, session_id, os_user, db_user, client_info, module_ctx, action_ctx, created_at ) VALUES ( p_module, SQLCODE, SQLERRM, DBMS_UTILITY.FORMAT_CALL_STACK, -- who called whom at error time DBMS_UTILITY.FORMAT_ERROR_STACK, -- ORA codes and messages (up to 2000 bytes) DBMS_UTILITY.FORMAT_ERROR_BACKTRACE, -- original line numbers preserved through re-raises p_parameters, SYS_CONTEXT('USERENV', 'SESSIONID'), SYS_CONTEXT('USERENV', 'OS_USER'), SYS_CONTEXT('USERENV', 'SESSION_USER'), SYS_CONTEXT('USERENV', 'CLIENT_INFO'), SYS_CONTEXT('USERENV', 'MODULE'), SYS_CONTEXT('USERENV', 'ACTION'), SYSTIMESTAMP ); COMMIT; -- autonomous transaction commit β does not affect caller's transaction EXCEPTION WHEN OTHERS THEN -- If error logging itself fails, write to alert log and continue -- Never let logging failure suppress the original error SYS.DBMS_SYSTEM.KSDWRT( 2, -- alert log destination 'ERROR LOG FAILED in capture_error for module=' || p_module || ' SQLCODE=' || SQLCODE ); END capture_error; / -- =================================================== -- Usage pattern: every production exception handler -- =================================================== CREATE OR REPLACE PROCEDURE io.thecodeforge.payment.reconcile_batch( p_batch_id IN NUMBER, p_run_date IN DATE ) IS v_processed NUMBER := 0; v_failed NUMBER := 0; BEGIN -- Business logic FOR rec IN ( SELECT * FROM io.thecodeforge.payment.pending_transactions WHERE batch_id = p_batch_id ) LOOP BEGIN io.thecodeforge.payment.process_transaction( p_txn_id => rec.transaction_id, p_batch_id => p_batch_id ); v_processed := v_processed + 1; EXCEPTION WHEN OTHERS THEN -- Capture: stack + parameters + context io.thecodeforge.logging.capture_error( p_module => 'RECONCILE_BATCH.LOOP', p_parameters => 'batch_id=' || p_batch_id || CHR(10) || 'run_date=' || TO_CHAR(p_run_date, 'YYYY-MM-DD') || CHR(10) || 'txn_id=' || rec.transaction_id || CHR(10) || 'processed_so_far=' || v_processed ); v_failed := v_failed + 1; -- For batch processing: log-and-continue pattern -- Do NOT re-raise here β allows batch to process remaining records END; END LOOP; -- Report completion with counts io.thecodeforge.logging.capture_error( p_module => 'RECONCILE_BATCH.SUMMARY', p_parameters => 'batch_id=' || p_batch_id || CHR(10) || 'processed=' || v_processed || CHR(10) || 'failed=' || v_failed ); COMMIT; END reconcile_batch; / -- =================================================== -- Post-mortem query: find the failing call chain -- Run this after a production error to reconstruct the stack -- =================================================== SELECT el.error_id, el.module_name, el.error_code, el.error_message, el.parameters, el.call_stack, el.error_backtrace, el.db_user, el.os_user, el.created_at FROM io.thecodeforge.logging.error_log el WHERE el.created_at > SYSTIMESTAMP - INTERVAL '1' HOUR AND el.error_code != 0 -- exclude informational entries ORDER BY el.created_at DESC FETCH FIRST 20 ROWS ONLY;
- FORMAT_CALL_STACK: shows the procedure call chain at the moment capture_error was called β equivalent to the debugger's Call Stack window
- FORMAT_ERROR_STACK: shows the ORA error codes and messages including ORA-06512 lines β up to 2000 bytes (use when SQLERRM's 512-byte limit truncates deep stacks)
- FORMAT_ERROR_BACKTRACE: shows the original line number where the error was first raised, preserved through re-raises β this is the one that gives you the exact line even when multiple RAISE statements are in the propagation path
- Log all three, always β each provides information the others do not
- AUTONOMOUS_TRANSACTION is non-negotiable for error logging β without it, the ROLLBACK triggered by the error deletes your log record
- Log input parameters in a structured format (key=value separated by CHR(10)) so you can parse them programmatically and reproduce the exact failure condition in a test environment
| Approach | Time to Diagnosis | Environment | Context Quality | Best For |
|---|---|---|---|---|
| Manual ORA-06512 text parsing | Slow (30β90 min per cycle) | Any | Low β line numbers only; line numbers shift with code changes | Single-procedure scripts with no nested calls; debugging on a machine where SQL Developer is unavailable |
| DBMS_OUTPUT.PUT_LINE logging | Slow (15β30 min per cycle) | Any with output enabled | Low to Medium β only what you remembered to log; requires redeploy per iteration | Legacy codebases without a modern error log table; quick smoke-check of a known variable value |
| SQL Developer local debugger | Fast (2β5 min first attempt) | Local dev schema | Highest β full interactive variable state, navigable Call Stack, Watch expressions | Development environment; errors reproducible with local test data; any new ORA-06512 investigation |
| SQL Developer remote debugger | Medium (5β15 min setup + 2β5 min diagnosis) | Test/Staging | Highest β identical to local, with 200β500 ms per-step overhead | Environment-specific errors that require staging data volume or configuration |
| Exception breakpoints (any connection) | Fastest (under 3 min from breakpoint set to root cause) | Local or Remote | Highest β halts at the exact raising line with full pre-handler variable state | Known error code investigation; eliminating manual line-number correlation entirely |
| FORMAT_CALL_STACK + error log table | Fast (post-mortem, under 5 min to read the log) | Production or any automated job | Medium β stack text and parameters but no interactive variable inspection | Production errors; unattended batch jobs; intermittent errors captured on first occurrence |
| Conditional breakpoints | Medium setup, then fastest (fires only on matching data) | Local or Remote | Highest β full interactive state, filtered to the specific data condition | Intermittent errors tied to specific parameter values; batch jobs with millions of successful iterations before the error |
π― Key Takeaways
- Exception breakpoints halt execution at the exact line that raises the error β eliminating manual ORA-06512 line number parsing entirely and reducing diagnosis time from 30β90 minutes to under 5 minutes
- Both conditions must be true before debugging works: correct privilege grants AND DEBUG compilation of every procedure in the call chain β missing either produces silent failure with no error message
- The Call Stack window replaces textual ORA-06512 lines with a clickable navigable view β click any frame to jump to that source line and inspect that scope's variables
- Remote debugging architecture: the database session initiates outbound TCP to SQL Developer, not the reverse β SQL Developer must be listening before DBMS_DEBUG_JDWP.CONNECT_TCP is called
- In production, use FORMAT_CALL_STACK + FORMAT_ERROR_STACK + FORMAT_ERROR_BACKTRACE in an AUTONOMOUS_TRANSACTION logging procedure β log input parameters alongside the stack to enable exact failure reproduction in a test environment
- Always revert DEBUG compilation before promoting to production β add a CI/CD gate that checks debuginfo = 'YES' and fails the pipeline if any production-bound object is still in debug mode
β Common Mistakes to Avoid
Interview Questions on This Topic
- QHow would you set up SQL Developer to debug an ORA-01403 error that occurs at line 142 in a three-level nested procedure call chain?SeniorReveal
- QWhat is the difference between DBMS_UTILITY.FORMAT_CALL_STACK, FORMAT_ERROR_STACK, and FORMAT_ERROR_BACKTRACE, and when would you log each one?Mid-levelReveal
- QHow would you debug an ORA-06512 error in production where you cannot attach SQL Developer's interactive debugger?Mid-levelReveal
- QWhat privileges are required for SQL Developer debugging and why might breakpoints still not work even after granting them?JuniorReveal
- QExplain the architecture of SQL Developer remote debugging β which side initiates the TCP connection and why does the firewall direction matter?SeniorReveal
Frequently Asked Questions
Can I use SQL Developer debugger against a production database?
You should not, for three reasons: (1) DEBUG compilation must be active for breakpoints to work, and debug objects should not exist in production β they expose internal variable names and are larger than necessary; (2) the JDWP debug connection holds an open session and adds latency to every step, which affects production workloads; (3) the risk of accidentally modifying data or leaving the session in a mid-transaction state during debugging is not acceptable in production. Instead, implement enhanced error logging using DBMS_UTILITY.FORMAT_CALL_STACK, FORMAT_ERROR_STACK, and FORMAT_ERROR_BACKTRACE in AUTONOMOUS_TRANSACTION blocks. Use the logged parameters to reproduce the issue in a staging environment where you can attach the debugger safely.
Why does my breakpoint not trigger even though the procedure is compiled with DEBUG?
Work through this checklist: (1) Verify the compiled version matches your current source β if you edited the procedure after compiling with DEBUG, recompile: ALTER PROCEDURE proc_name COMPILE DEBUG; (2) Confirm the connection in SQL Developer uses the correct schema owner β a breakpoint set in SCHEMA_A.PROC will not fire when SCHEMA_B.PROC executes, even if they have identical source; (3) Verify you have DEBUG privilege on the object: SELECT * FROM dba_tab_privs WHERE grantee = 'YOUR_USER' AND privilege = 'DEBUG'; (4) For remote debugging, confirm DBMS_DEBUG_JDWP.CONNECT_TCP was called before the procedure executed and SQL Developer had already started listening; (5) Check the Breakpoints window β the breakpoint must show as active (enabled checkbox) and not disabled or skipped.
How do I debug a trigger that raises ORA-06512?
Triggers are debuggable but require a different approach. First, compile the trigger with DEBUG: ALTER TRIGGER trigger_name COMPILE DEBUG. Set a line breakpoint inside the trigger body at the line you want to inspect, or set an exception breakpoint for the expected error code. Then perform the DML operation that fires the trigger (INSERT, UPDATE, or DELETE) through the debug session. The debugger halts when the trigger executes. The Call Stack window will show the trigger as a frame in the stack. One limitation: the Call Stack context for triggers is less detailed than for standalone procedures β the triggering DML is shown as the parent frame but without parameter details. Use the Variables window to inspect trigger-level variables including :NEW and :OLD record values.
What is the performance impact of DEBUG compilation in production?
DEBUG compilation increases object size by 10β15% due to embedded symbol tables that map bytecode positions to source lines and variable names. Runtime execution performance when no debugger is attached is identical to non-debug compilation β the symbol table is only read when a debugger connects and requests information. The overhead only appears during an active debug session when breakpoints are hit: each breakpoint hit requires the debugger to evaluate the symbol table and transfer variable values over the JDWP connection, which takes 200β500 ms per hit in remote sessions. None of this overhead applies in production unless someone accidentally attaches a debug session to a production database β which is why you should never have DEBUG compilation in production regardless of the performance argument.
Can I set a conditional breakpoint that only fires for specific parameter values?
Yes, and this is the correct approach for intermittent errors that occur only under specific data conditions. In the Breakpoints window, right-click a breakpoint and select Properties. In the Condition field, enter a PL/SQL boolean expression such as: p_customer_id IS NULL, p_report_type = 'QUARTERLY_ADJUSTMENT', or v_count = 0. The breakpoint fires only when the condition evaluates to TRUE. Conditions are evaluated at the breakpoint location β variables must be in scope. For exception breakpoints, the condition is evaluated at the moment the exception is raised, using the variables in scope at that point. Use conditional breakpoints for batch jobs that process millions of records successfully before hitting the one record that triggers the error β without the condition, the debugger would halt on every matching exception, making the session unusable.
How long does DBMS_DEBUG_JDWP.CONNECT_TCP block if SQL Developer is not listening?
CONNECT_TCP blocks for the number of seconds specified in the timeout parameter. The default timeout is implementation-dependent but typically 30β60 seconds if not specified. After the timeout expires, CONNECT_TCP raises an exception (ORA-30671 or a timeout-related error) and execution continues. The defensive wrapper pattern in this guide catches this exception and continues execution without the debugger attached β useful in test automation where debugger attachment is optional. Always specify an explicit timeout: DBMS_DEBUG_JDWP.CONNECT_TCP(host => '...', port => 4000, timeout => 30). A blocked CONNECT_TCP call with no timeout can hold up a database session indefinitely if SQL Developer never attaches.
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.