Robust error and exception handling is a critical discipline for professional Oracle developers. When anomalies occur, application stability and data integrity depend on our ability to respond appropriately. However, the default exception handling tools provided in PL/SQL can often be too generic and limited. This is where the flexible raise_application_error procedure shines…

The Hidden Costs of Unhandled Exceptions

Exceptions are a fact of life in any large-scale software system. Whether triggered by invalid user input, database constraints, network outages, or internal logical errors, exception conditions in Oracle databases can have wide-ranging impacts:

  • Cryptic, meaningless errors frustrate users and damage productivity
  • Important diagnostic context gets lost diluting root cause analysis
  • Cascading failures and transactions corruption introduce data risks
  • Difficult-to-diagnose bugs hide in plain sight awaiting triggers

By one estimate, up to 40% of total development effort goes into exception handling activities ([Lee 2002]). Yet still, unhandled or misunderstood exceptions continue to plague applications.

While PL/SQL provides basic exception handling capabilities out-of-the-box, relying solely on built-in tools has drawbacks:

  • Generic ORA-06510 and ORA-06508 errors reveal little about root issues
  • Traceback logs get flooded with volumes of identical errors
  • Granular exception management control is limited
  • No custom alerts or application-level responses

In the absence of a robust exception handling approach, costs can spiral:

Exception Handling Costs

Figure 1 – Spiraling costs of unhandled exceptions in enterprise systems

Raise_Application_Error to the Rescue

Raise_application_error enables developers to take back control over exceptions by raising custom error codes and messages within PL/SQL blocks.

Unlike built-in exceptions, using raise_application_error delivers:

  • Meaningful Context: Describe exactly what went wrong in detail
  • Actionable Errors: Communicate issues clearly to end-users
  • Noise Reduction: Eliminate volumes of generic tracebacks
  • metadata: Embed context-specific data directly in errors
  • Diagnostic Precision: Quickly pinpoint root causes

With custom exceptions, we move from vague post-mortems to laser-focused diagnoses.

A World Without Raise_Application_Error

To demonstrate the power of raise_application_error in action, let‘s visualize a scenario without custom exceptions…

CREATE PROCEDURE apply_discount 
(
  order_id IN NUMBER,
  discount IN NUMBER 
)
IS
BEGIN
  UPDATE orders
  SET discount = discount + :discount
  WHERE id = :order_id;

  INSERT INTO order_events(order_id, event)
    VALUES(:order_id, ‘Discount applied‘);

EXCEPTION
  WHEN OTHERS THEN
    -- log error
    INSERT INTO errors(code, message)
    VALUES(SQLCODE, SQLERRM);  
END apply_discount;

If a CHECK constraint is violated, we log a generic error to the errors table:

CODE MESSAGE
-1400 ORA-01400: cannot insert NULL into ("SCHEMA"."ORDERS"."ID")

This reveals little about the root cause. Now imagine hundreds of identical errors flooding the logs daily – isolating the original trigger becomes needlessly difficult.

Meanwhile, any developers inheriting maintenance of apply_discount() must wade through reams of code and constraints attempting to debug the issue through trial and error.

Without raise_application_error, we fail the software development "bus test" – if developers were hit by a bus, could a new team readily understand and maintain the code?

Enrich Errors with Raise_Application_Error

With raise_application_error, we can enrich errors with custom context to support diagnostics and prevent obscurity.

Revisiting our example:

CREATE PROCEDURE apply_discount
(
  order_id IN NUMBER,
  discount IN NUMBER  
)
IS
BEGIN
  UPDATE orders
  SET discount = discount + :discount
  WHERE id = :order_id;

  IF SQL%NOTFOUND THEN
    RAISE_APPLICATION_ERROR(-20000, 
      ‘Invalid order_id ‘ || order_id);
  END IF;

  INSERT INTO order_events(order_id, event)
  VALUES(:order_id, ‘Discount applied‘);

EXCEPTION
  WHEN OTHERS THEN

    IF SQLCODE = -20000 THEN
      -- log user-defined error
      INSERT INTO errors(code, message) 
      VALUES(SQLCODE, SQLERRM);

    ELSE
      -- log unhandled exceptions
      INSERT INTO errors(code, message)
      VALUES(SQLCODE, DBMS_UTILITY.FORMAT_ERROR_STACK);

    END IF;

END apply_discount;

Now if an invalid order_id is passed, the errors table captures:

CODE MESSAGE
-20000 Invalid order_id 101

The root cause is immediately clear! No more searching transaction logs or decoding obscurity.

We can even standardize error codes so they directly indicate the domain area, further improving understandability:

Code Area
-20100 – -20199 Inventory Errors
-20500 – -20599 Fulfillment Errors
-20000 – -20400 Order Processing Errors

With meaningful errors encoded directly into exceptions through raise_application_error, diagnosing and handling issues becomes far simpler.

Balancing User Experience with Diagnostics

While our example focused on improving internal diagnostics, keep in mind that raise_application_error also enables user-friendly application messaging.

Whereas generic exceptions frustrate end-users, clear, actionable errors promote satisfaction by setting proper expectations ([Bulger 2015]).

However, strike a balance between usability and logging. Logging the full error stack provides a forensic trail for admins even if users receive a simplified message.

Risks of Overusing Custom Errors

While raise_application_error is a powerful tool for enriching exceptions, beware overusing custom errors for multiple discrete issues.

Too many distinct error codes can complicate correlations and metrics. Instead, aim to provide a manageable catalog of error variants addressing core categories of issues.

Also resist hiding unexpected system failures using generic custom codes as this masks potentially serious problems. Log native exceptions to audit logs even when recasting issues for users.

Alternate Techniques

If implementing centralized logging and monitoring, application-level APIs may sometimes be preferable to raise_application_error:

CREATE PROCEDURE backorder_item
(
  order_id NUMBER,
  item_id NUMBER
) 
IS
BEGIN

  IF inventory_low(item_id) THEN

    -- raise custom exception
    RAISE_APPLICATION_ERROR(-20501,‘Insufficient inventory for Item ‘ || item_id);    

  ELSE

    -- call API
    backorder_svc.register_backorder(order_id, item_id);

  END IF;

END;

Here, an API call may allow propagating the error to monitoring systems without aborting transactions.

Similarly, customizable EXECUTE IMMEDIATE exception blocks offer greater control for handling anticipated error categories:

BEGIN
  EXECUTE IMMEDIATE ‘INSERT INTO orders VALUES(101, NULL)‘;
EXCEPTION
  WHEN OTHERS THEN
    IF SQLCODE = -1400 THEN
      -- handle constraint violation
    ELSE
      RAISE; -- rethrow all others
    END IF;
END;

Finally, consider combining declarative exception handlers targeting specific exception types with raise_application_error for application-specific cases:

DECLARE

  null_value EXCEPTION;
  PRAGMA EXCEPTION_INIT(null_value, -1400);

BEGIN

  -- constraint violation raises built-in handler  
  INSERT INTO orders(id, amount) VALUES(101, NULL); 

EXCEPTION 

  WHEN null_value THEN
    -- handle expected constraint error

  WHEN OTHERS THEN 

    -- raise app exception for unanticipated issues
    RAISE_APPLICATION_ERROR(-20251, ‘Unhandled error inserting orders row‘);

END;

This balances standardization with domain-specific enrichment.

Debugging Raise_Application Errors

When debugging custom exceptions, a simple technique is enabling debugging modules to dump your error stack on failure:


ALTER SESSION SET PLSQL_WARNINGS = ‘ENABLE:ALL‘;

BEGIN

  RAISE_APPLICATION_ERROR(-20404, ‘Order Cannot Be Fulfilled‘);

EXCEPTION
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE(DBMS_UTILITY.FORMAT_ERROR_BACKTRACE);

END;

The module DBMS_UTILITY formats the full traceback to help identify trigger points.

Further, combining error logging with temporary DBMS_OUTPUT statements or an interactive debugger like SQL Developer is invaluable for diagnosing issues.

Monitoring Error Metrics with ADR

To track metrics on raise_application_errors, Oracle provides an automated diagnostics repository (ADR) for aggregating exceptions and traces.

After enabling, exceptions can be queried across systems:

-- Check for frequent errors
SELECT error_code, count(1) 
  FROM dba_adrer_exceptions
  GROUP BY error_code;

-- Scan full detail on specific codes  
SELECT * FROM dba_adrer_exceptions
  WHERE error_code = -20500;

Analyzing trends using ADR highlights usage patterns, catch missing handlers, identify performance issues triggered by errors, and more.

For code vulnerable to recurring issues, metrics offer an invaluable feedback loop for continual improvement.

Best Practices

When leveraging raise_application_error for custom exceptions, keep these best practices in mind:

Namespace Errors

Prefix custom error numbers by functionality to identify subsystems and isolate domains. This improves context when reviewing logs.

Fail Fast

Check prerequisites early and raise exceptions immediately when known illegal/unsupported states are found.

Provide Actionable Context

Explanation messages should provide clear direction rather than generic apologies to improve usability.

Reuse Shared Messages

Consider centralizing common messages into lookup tables that can be reused across exceptions to normalize.

Cast Carefully

Only recast unexpected system exceptions after carefully validating there are no deeper issues being masked.

Enrich, Don’t Obfuscate

Supplement native exceptions with additional context through error tracing rather than completely hiding underlying problems from administrators.

Monitor Effectiveness

Enable error instrumentation via ADR and trace logs to measure whether exception handling changes are having the desired impact.

Conclusion

Mastering exception handling discipline is critical for sustainable enterprise Oracle development. Unhandled errors introduce unnecessary friction through wasted diagnostics effort, poor user experiences, and reliability gaps.

By embracing raise_application_error for enriching exceptions with custom context, we can reduce friction while accelerating root cause analysis. The result is improved stability and productivity.

But precision error handling requires practice and experience. As experts, we have an obligation to lead by example producing easily diagnosable systems rather than perpetuate obscurity through negligence. Raise your exception handling game today and transform how your teams deliver Oracle solutions!

References

Lee, G. (2002). Software Development Costs with and without Exception Handling. Proc. APSEC ‘02. IEEE, pp. 284-290

Bulger, C. (2015). The Importance of Exceptional Error Handling. DevPro Journal. Accessed Feb 2023: <link>

Oracle. (2006). Exception Handling and Invoking Procedures. Accessed Feb 2023: <link>

Similar Posts