PL/SQL Exception Handling — Silent Data Loss, NO_DATA_FOUND
Missing FK triggers NO_DATA_FOUND in loop; WHEN OTHERS with ROLLBACK wipes prior insert — batch success, data lost.
20+ years shipping high-throughput database systems. Lessons pulled from things that broke in production.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- PL/SQL exception handling separates error logic from business logic using an EXCEPTION block
- Three categories: predefined (NO_DATA_FOUND), non-predefined (mapped via PRAGMA), and user-defined (business rule violations)
- Performance: unhandled exceptions cause implicit ROLLBACK, which is slower than explicit error responses
- Production insight: WHEN OTHERS THEN NULL silently kills errors — you'll debug blind for hours
- Biggest mistake: Using SELECT INTO without a NO_DATA_FOUND handler in batch jobs — whole transaction rolls back on missing rows
Think of PL/SQL Exception Handling as a powerful tool in your developer toolkit. Once you understand what it does and when to reach for it, everything clicks into place. Imagine you are operating a complex manufacturing assembly line. Without exception handling, a single jammed gear causes the entire factory to explode. With exception handling, the system detects the jam, safely pauses that specific machine, logs the error for the mechanic, and allows the rest of the factory to keep running or shuts down gracefully without losing progress.
PL/SQL Exception Handling is a fundamental concept in Database development. In the world of Oracle programming, an exception is an error condition that occurs during the execution of a block. Effective handling ensures that your application remains stable even when the database encounters unexpected data or system issues.
In this guide we'll break down exactly what PL/SQL Exception Handling is, why it was designed to separate error logic from business logic, and how to use it correctly in real projects.
By the end you'll have both the conceptual understanding and practical code examples to use PL/SQL Exception Handling with confidence.
What Is PL/SQL Exception Handling and Why Does It Exist?
PL/SQL Exception Handling is a core feature of PL/SQL. It was designed to solve a specific problem: unhandled errors causing transaction failure and messy stack traces for the end-user. It exists to provide a centralized 'EXCEPTION' block at the end of your code, keeping your main execution path clean. There are three categories: Predefined (like NO_DATA_FOUND), Non-predefined (standard Oracle errors mapped to names), and User-defined (business rule violations). This architecture allows for graceful recovery, customized error logging, and consistent transaction management via ROLLBACK.
Types of Exceptions: Predefined, Non-Predefined, and User-Defined
Oracle provides a set of predefined exceptions like NO_DATA_FOUND (ORA-01403), TOO_MANY_ROWS (ORA-01422), DUP_VAL_ON_INDEX (ORA-00001), and others. These are automatically raised when the corresponding Oracle error occurs. Non-predefined exceptions cover standard Oracle errors that don't have a built-in name — you map them using PRAGMA EXCEPTION_INIT. User-defined exceptions are declared with the EXCEPTION keyword and raised explicitly with RAISE or RAISE_APPLICATION_ERROR. The choice depends on the error's source: predefined for common Oracle errors, non-predefined for specific ORA codes you want a name for, user-defined for your application's business rules.
Exception Propagation and Scope
Exceptions propagate from inner blocks to outer blocks until caught or until they reach the outermost block and become unhandled, causing the program to terminate. If an exception is raised in the declarative section (e.g., a variable initialisation fails), it cannot be caught by the same block's EXCEPTION section — it propagates immediately to the enclosing block. Understanding scope is critical: each BEGIN-END block has its own exception section. An exception raised in an inner block that is not handled there will propagate to the outer block's EXCEPTION section. You can use nested blocks to isolate errors and continue processing while still maintaining transactional integrity.
RAISE vs RAISE_APPLICATION_ERROR: When to Use Each
RAISE is used to re-raise the current exception or explicitly raise a user-defined exception declared with EXCEPTION. It preserves the original error stack. RAISE_APPLICATION_ERROR is a built-in procedure that raises a user-defined Oracle error with a custom message and optional flag to keep the error stack. Use RAISE_APPLICATION_ERROR when you want to raise a new error with a specific code (between -20000 and -20999) and message, especially when the caller expects a numeric error code. For simple business rule violations, a user-defined exception with RAISE is cleaner. For errors that need to be caught by external applications (like Java via JDBC), RAISE_APPLICATION_ERROR is better because it provides a standard Oracle error.
Common Mistakes and How to Avoid Them
When learning PL/SQL Exception Handling, most developers hit the same set of gotchas. The most dangerous is the 'Silent Failure'—using WHEN OTHERS THEN NULL; which swallows the error and makes debugging impossible. Another is failing to ROLLBACK a failed transaction, leaving the database in an inconsistent state.
Knowing these in advance saves hours of debugging production outages and prevents data corruption.
Best Practices for Production-Ready Exception Handling
Beyond the basics, robust exception handling in production requires logging, transaction management, and consistent propagation. Create a central error logging procedure that captures SQLCODE, SQLERRM, backtrace (DBMS_UTILITY.FORMAT_ERROR_BACKTRACE), and timestamp. Use autonomous transactions for error logging so that log inserts survive a rollback. Always re-raise exceptions after logging unless the error is truly recoverable. For batch processing, use SAVEPOINT and ROLLBACK TO SAVEPOINT to isolate failed rows. Handle COM (Communication) and TIMEOUT_ON_RESOURCE separately from application errors.
- Autonomous transaction ensures the log persists even if the outer transaction rolls back.
- DBMS_UTILITY.FORMAT_ERROR_BACKTRACE gives the exact line number — essential for debugging.
- Always re-raise after logging unless the error is expected and recoverable.
- Use WHEN OTHERS THEN log_error(...); RAISE; as a universal pattern.
The Execution Flow: What Happens When an Exception Is Raised?
You need to understand the sequence, not just the syntax. When an error occurs in the executable section of a PL/SQL block, control immediately jumps to the EXCEPTION section. The remaining code in the executable block is skipped entirely. No rollback happens automatically. Transaction control is your job. If the exception is handled, the block completes normally. If it's not handled, the exception propagates up to the calling block or client. This is why you must decide where to catch and where to let it bubble. A common production mistake: assuming the block resumes after handling. It does not. The executable block is aborted. Plan your logic accordingly.
Logging Errors Without Losing the Stack: SQLCODE and SQLERRM
You cannot fix what you do not see. The implicit variables SQLCODE and SQLERRM give you the numeric error code and the full error message from the last raised exception. Use them. Always log them in your exception handler. Never rely on DBMS_OUTPUT.PUT_LINE alone; that goes to the client console and disappears. In production, write to an error log table. Wrap the logging in an autonomous transaction (PRAGMA AUTONOMOUS_TRANSACTION) so the log persists even if the parent transaction rolls back. Why? Because if you do not log, you are blind during postmortem. The junior who skips this will spend hours guessing what went wrong. You will spend minutes reading the log.
Silent Data Loss in a Nightly Batch Job
- Always handle NO_DATA_FOUND explicitly where it occurs — don't let it bubble up blindly.
- Never use ROLLBACK inside an exception handler in a loop unless you also re-raise or exit the loop.
- Use SAVEPOINTs inside iterative processing to isolate failures to a single row.
- Test batch jobs with intentionally missing data to verify exception scope behaviour.
SET SERVEROUTPUT ON;DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM || ' code: ' || SQLCODE);| File | Command / Code | Purpose |
|---|---|---|
| ForgeExceptionDemo.sql | DECLARE | What Is PL/SQL Exception Handling and Why Does It Exist? |
| ExceptionTypes.sql | DECLARE | Types of Exceptions |
| NestedScope.sql | DECLARE | Exception Propagation and Scope |
| RaiseComparison.sql | DECLARE | RAISE vs RAISE_APPLICATION_ERROR |
| CommonMistakes.sql | BEGIN | Common Mistakes and How to Avoid Them |
| ErrorLogProcedure.sql | CREATE OR REPLACE PACKAGE io_thecodeforge.error_log_pkg AS | Best Practices for Production-Ready Exception Handling |
| employee_update.sql | DECLARE | The Execution Flow |
| error_logger.sql | CREATE TABLE error_log ( | Logging Errors Without Losing the Stack |
Key takeaways
Interview Questions on This Topic
What is the difference between SQLCODE and SQLERRM in an exception block?
Frequently Asked Questions
20+ years shipping high-throughput database systems. Lessons pulled from things that broke in production.
That's PL/SQL. Mark it forged?
3 min read · try the examples if you haven't