Exception Handling

All exception classes inherit from CX_ROOT through one of three categories:

23.1 Class-based exceptions

All exception classes inherit from CX_ROOT through one of three categories:

Must be declared in RAISING?

Checked by compiler Yes (statically) Expected errors the caller must handle Yes, but checked only at runtime

Errors the caller can avoid (e.g. CX_SY_ZERODIVIDE) No (implicitly declared)

Fatal/system errors (e.g. resource exhaustion)

23.2 TRY / CATCH / CLEANUP

CATCH order matters: list subclasses before superclasses (CX_ROOT last).

CATCH BEFORE UNWIND keeps the context alive (for resumable exceptions).

RETRY (in a CATCH block) restarts the TRY block; RESUME continues after a RAISE RESUMABLE EXCEPTION.

lx->get_text( ), lx->get_longtext( ), lx->previous (chained cause), lx->get_source_position( ).

TRY.
    DATA(lv_result) = 10 / lv_divisor.
    DATA(ls_row) = lt_orders[ order_id = '999' ].
    lo_account->withdraw( 1000 ).
  CATCH cx_sy_zerodivide INTO DATA(lx_div).
    MESSAGE lx_div->get_text( ) TYPE 'I'.
  CATCH cx_sy_itab_line_not_found.
    MESSAGE 'Order not found' TYPE 'I'.
  CATCH zcx_insufficient_funds cx_sy_arithmetic_overflow INTO DATA(lx_any).
    MESSAGE lx_any TYPE 'I'.
  CLEANUP.
    " runs when an exception leaves this TRY block without being caught here
    lo_resource->close( ).
ENDTRY.

23.3 Defining an exception class

In SE24/ADT create ZCX_INSUFFICIENT_FUNDS inheriting from CX_STATIC_CHECK, implementing IF_T100_MESSAGE (or IF_T100_DYN_MSG) to use message-class texts:

CLASS zcx_insufficient_funds DEFINITION PUBLIC
  INHERITING FROM cx_static_check FINAL CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES if_t100_dyn_msg.
    DATA mv_amount TYPE string READ-ONLY.
    METHODS constructor
      IMPORTING textid    LIKE if_t100_message=>t100key OPTIONAL
                previous  LIKE previous OPTIONAL
                iv_amount TYPE string OPTIONAL.
ENDCLASS.

CLASS zcx_insufficient_funds IMPLEMENTATION.
  METHOD constructor ##ADT_SUPPRESS_GENERATION.
    super->constructor( previous = previous ).
    mv_amount = iv_amount.
    CLEAR me->textid.
    IF textid IS INITIAL.
      if_t100_message~t100key = VALUE #( msgid = 'ZBANK' msgno = '001' attr1 = 'MV_AMOUNT' ).
    ELSE.
      if_t100_message~t100key = textid.
    ENDIF.
  ENDMETHOD.
ENDCLASS.

23.4 Raising exceptions

RAISE EXCEPTION TYPE zcx_insufficient_funds EXPORTING iv_amount = |{ iv_amount }|.  " classic
RAISE EXCEPTION NEW zcx_insufficient_funds( iv_amount = |{ iv_amount }| ).          " 7.52+
" With message directly (IF_T100_DYN_MSG)
RAISE EXCEPTION TYPE zcx_order_error MESSAGE e005(zorders) WITH lv_order_id.
" In expressions
DATA(lv_x) = COND #( WHEN lv_ok = abap_true THEN 1 ELSE THROW zcx_order_error( ) ).
" Wrap a lower-level exception
CATCH cx_sy_open_sql_db INTO DATA(lx_db).
  RAISE EXCEPTION NEW zcx_order_error( previous = lx_db ).

23.5 Classic (non-class-based) exceptions

Function modules and old methods use EXCEPTIONS and set sy-subrc:

Class-based and classic exceptions cannot be mixed in one procedure's interface. sy-subrc is also the classic return code for statements (READ TABLE, SELECT, AUTHORITY-CHECK, …): 0 = success.

" Inside a function module
RAISE not_found.                                  " ends FM, caller gets sy-subrc
MESSAGE e001(zmsg) RAISING not_found.             " sets sy-msg* too
" Caller
CALL FUNCTION 'Z_FM' EXCEPTIONS not_found = 1 OTHERS = 2.

23.6 Runtime errors

Uncaught exceptions produce a short dump (ST22) with the source position, call stack, and variable values. Catchable runtime errors are mapped to CX_SY_* classes (e.g. CX_SY_CONVERSION_NO_NUMBER, CX_SY_OPEN_SQL_DB, CX_SY_REF_IS_INITIAL). MESSAGE … TYPE 'X' deliberately dumps.

23.7 Guidelines

Use class-based exceptions in new code; one exception class per error family with texts from message classes.

Don't catch CX_ROOT and swallow it silently; log it (application log, BAL_* function modules or CL_BALI_LOG in ABAP Cloud).

Prefer CX_STATIC_CHECK for business errors, CX_NO_CHECK sparingly.

Convert technical exceptions to meaningful business exceptions at layer boundaries, keeping previous.

🧪 Part II Exercises

Build a hashed table of 100,000 generated customers and compare the runtime of key lookups against a standard table using GET RUN TIME FIELD.

Given a table of sales (region, product, amount), use LOOP AT … GROUP BY to print totals per region and per region/product; repeat with AT NEW and compare.

Using only VALUE, FOR, REDUCE, and FILTER, compute: the list of product names in upper case, the total revenue, and the sales above the average.

Write a function module Z_CALC_ORDER_TOTAL with class-based exceptions and call it from a report.

Write an ABAP SQL query that joins SFLIGHT, SPFLI, and SCARR to list, per carrier name, the number of flights and the average occupancy percentage, ordered descending.

Create a lock object for ZORDERS and a report that locks an order, waits for user confirmation (popup), changes the status, commits, and unlocks. Open it twice to observe the lock in SM12.

Build a selection-screen report with a mandatory carrier, a date range defaulting to the last 30 days, radio buttons for ALV vs. list output, and validation in AT SELECTION-SCREEN.

Display the result of exercise 7 with CL_SALV_TABLE, including totals, a hidden column, renamed column headers, and a double-click handler.

Model Employee → Manager / Developer with an abstract calculate_bonus method and an interface lif_reportable; process a mixed table polymorphically.

Create a custom exception class with a message-class text and attributes; raise it from a method, catch it two levels up, and display the message with its previous chain.