Exception Handling
PL/SQL's exception-handling model lets you catch runtime errors gracefully, log them, recover where possible, and propagate them to callers when recovery is not possible. An unhandled exception rolls back the current statement (not the whole transaction) and propagates up the call stack.
Predefined Exceptions
Oracle defines named exceptions for the most common errors. You can catch them by name in the EXCEPTION block:
| Exception | ORA code | When raised |
|---|---|---|
NO_DATA_FOUND |
ORA-01403 | SELECT INTO returns no rows |
TOO_MANY_ROWS |
ORA-01422 | SELECT INTO returns more than one row |
DUP_VAL_ON_INDEX |
ORA-00001 | Unique/primary key violation |
ZERO_DIVIDE |
ORA-01476 | Division by zero |
VALUE_ERROR |
ORA-06502 | Arithmetic, conversion, truncation error |
INVALID_CURSOR |
ORA-01001 | Illegal cursor operation (e.g., fetch after close) |
CURSOR_ALREADY_OPEN |
ORA-06511 | Opening an already-open cursor |
INVALID_NUMBER |
ORA-01722 | Implicit string-to-number conversion fails |
LOGIN_DENIED |
ORA-01017 | Invalid username/password |
NOT_LOGGED_ON |
ORA-01012 | Not connected to Oracle |
TIMEOUT_ON_RESOURCE |
ORA-00051 | Timeout waiting for a resource |
DECLARE
v_salary employees.salary%TYPE;
v_result NUMBER;
BEGIN
-- NO_DATA_FOUND
SELECT salary INTO v_salary FROM employees WHERE employee_id = 9999;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('Employee not found — defaulting to 0');
v_salary := 0;
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('Unexpected: multiple rows returned');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Unexpected error: ' || SQLERRM);
RAISE; -- re-raise to propagate to caller
END;
/
WHEN OTHERS
WHEN OTHERS catches any exception not matched by a preceding WHEN. It should always RAISE or RAISE_APPLICATION_ERROR after logging — silently swallowing exceptions hides bugs:
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Handle specifically
RETURN 0;
WHEN OTHERS THEN
-- Log the error
DBMS_OUTPUT.PUT_LINE(
'Error ' || SQLCODE || ': ' || SQLERRM
);
-- Then propagate
RAISE;
END;
WHEN OTHERS THEN NULL; is the worst antipattern in PL/SQL — it silently discards every error, making debugging nearly impossible. Always log and re-raise in WHEN OTHERS.
User-Defined Exceptions
Declare and raise your own exceptions for business rule violations:
DECLARE
e_salary_too_high EXCEPTION;
e_invalid_dept EXCEPTION;
v_salary NUMBER := 300000;
v_dept_id NUMBER := 999;
BEGIN
IF v_salary > 250000 THEN
RAISE e_salary_too_high;
END IF;
IF NOT EXISTS (SELECT 1 FROM departments WHERE department_id = v_dept_id) THEN
RAISE e_invalid_dept;
END IF;
EXCEPTION
WHEN e_salary_too_high THEN
DBMS_OUTPUT.PUT_LINE('Salary $' || v_salary || ' exceeds company cap.');
WHEN e_invalid_dept THEN
DBMS_OUTPUT.PUT_LINE('Department ' || v_dept_id || ' does not exist.');
END;
/
PRAGMA EXCEPTION_INIT
Associates a user-defined exception name with an Oracle error code, giving a named handle to system errors not covered by predefined exceptions:
DECLARE
e_deadlock EXCEPTION;
e_null_not_allowed EXCEPTION;
PRAGMA EXCEPTION_INIT(e_deadlock, -60); -- ORA-00060
PRAGMA EXCEPTION_INIT(e_null_not_allowed, -1400); -- ORA-01400
BEGIN
INSERT INTO employees (employee_id) VALUES (NULL); -- violates NOT NULL
EXCEPTION
WHEN e_null_not_allowed THEN
DBMS_OUTPUT.PUT_LINE('Cannot insert NULL for employee_id.');
WHEN e_deadlock THEN
DBMS_OUTPUT.PUT_LINE('Deadlock detected — retrying...');
END;
/
RAISE_APPLICATION_ERROR
Raises a user-defined error with a custom ORA error number (-20000 to -20999) and message. The error appears exactly like an Oracle system error to callers:
CREATE OR REPLACE PROCEDURE update_salary(
p_emp_id IN employees.employee_id%TYPE,
p_new_sal IN employees.salary%TYPE
) IS
v_max_salary CONSTANT NUMBER := 250000;
v_min_salary CONSTANT NUMBER := 2000;
BEGIN
IF p_new_sal > v_max_salary THEN
RAISE_APPLICATION_ERROR(
-20001,
'Salary $' || p_new_sal || ' exceeds maximum of $' || v_max_salary
);
END IF;
IF p_new_sal < v_min_salary THEN
RAISE_APPLICATION_ERROR(
-20002,
'Salary $' || p_new_sal || ' is below minimum of $' || v_min_salary
);
END IF;
UPDATE employees SET salary = p_new_sal WHERE employee_id = p_emp_id;
IF SQL%NOTFOUND THEN
RAISE_APPLICATION_ERROR(-20003, 'Employee ' || p_emp_id || ' not found');
END IF;
COMMIT;
END update_salary;
/
The second parameter to RAISE_APPLICATION_ERROR can be TRUE to add the error to the existing error stack, or FALSE (default) to replace the stack.
SQLCODE and SQLERRM
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Code: ' || SQLCODE); -- e.g., -1403
DBMS_OUTPUT.PUT_LINE('Message: ' || SQLERRM); -- e.g., ORA-01403: no data found
DBMS_OUTPUT.PUT_LINE('Backtrace: ' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);
RAISE;
END;
| Function | Returns |
|---|---|
SQLCODE |
Numeric error code (negative for Oracle errors, 0 for success, +100 for NO_DATA_FOUND) |
SQLERRM |
Error message string |
SQLERRM(code) |
Error message for a specific error code |
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE |
Stack trace showing which line raised the exception |
DBMS_UTILITY.FORMAT_ERROR_STACK |
Full error stack (like SQLERRM but longer) |
Exception Propagation
CREATE OR REPLACE PROCEDURE inner_proc IS
BEGIN
-- This raises NO_DATA_FOUND
DECLARE v_x NUMBER;
BEGIN
SELECT salary INTO v_x FROM employees WHERE employee_id = 9999;
END;
-- Not caught here — propagates to inner_proc's EXCEPTION block
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Handle it here OR re-raise with RAISE
RAISE_APPLICATION_ERROR(-20099, 'Employee lookup failed in inner_proc');
END inner_proc;
/
CREATE OR REPLACE PROCEDURE outer_proc IS
BEGIN
inner_proc; -- if inner_proc raises, exception propagates here
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(
'Caught in outer_proc: ' || SQLERRM || CHR(10) ||
DBMS_UTILITY.FORMAT_ERROR_BACKTRACE
);
END outer_proc;
/
Re-raising Exceptions
EXCEPTION
WHEN NO_DATA_FOUND THEN
-- Log, then re-raise the original exception unchanged
log_error('get_employee', SQLCODE, SQLERRM);
RAISE; -- re-raise preserves original error code and message
WHEN OTHERS THEN
log_error('get_employee', SQLCODE, SQLERRM);
RAISE_APPLICATION_ERROR(
-20100,
'Unexpected error in get_employee: ' || SQLERRM,
TRUE -- TRUE = keep original error stack
);
END;
Summary
- The
EXCEPTIONblock at the end of aBEGIN ... ENDcatches runtime errors. - Predefined named exceptions (
NO_DATA_FOUND,TOO_MANY_ROWS, etc.) match common Oracle errors. WHEN OTHERScatches everything else — always log andRAISEorRAISE_APPLICATION_ERROR.- Declare custom exceptions and
RAISEthem for business rule violations. PRAGMA EXCEPTION_INITassigns a name to any Oracle error code.RAISE_APPLICATION_ERROR(-20000..-20999, msg)creates structured user-defined errors visible to callers.SQLCODE,SQLERRM, andDBMS_UTILITY.FORMAT_ERROR_BACKTRACEgive full error context.RAISEre-raises the current exception unchanged;RAISE_APPLICATION_ERRORreplaces it.