Skip to Content

You know ABAP’s class based exception handling very well.

  • Exceptions are either raised explicitly with RAISE EXCEPTION or implicitly when ABAP statements go wrong,
  • Exceptions can be propagated from procedures using RAISING.
  • Exceptioms are handled in TRY blocks using CATCH and CLEANUP.

But did you ever hear about RETRY and RESUME? If yes, skip reading this blog. If not, read on …

Recap: Raising and Handling Exceptions

The following code sequence repeats the well known principles of raising and handling class based exceptions:

CLASS cx_demo DEFINITION INHERITING FROM cx_static_check.

ENDCLASS.

CLASS cls DEFINITION.

  PUBLIC SECTION.

    CLASS-METHODS meth RAISING cx_demo.

ENDCLASS.

CLASS cls IMPLEMENTATION.

  METHOD meth.

    …

    RAISE EXCEPTION TYPE cx_demo.

    …

  ENDMETHOD.

ENDCLASS.

START-OF-SELECTION.

  TRY.

      cls=>meth( ).

    CATCH cx_demo INTO DATA(exc).

      cl_demo_output=>display( `Handling ` &&

        cl_abap_classdescr=>get_class_name( p_object = exc ) ).

  ENDTRY.


  • An exception class is declared by inheriting from one of the predefined classes cx_static_check, cx_dynamic_check, cx_no_check.
    Their meaning is described in the documentation.
  • A method propagates the exception that it cannot handle to the caller using RAISING.
  • A method implementation can raise an exception with RAISE EXCEPTION TYPE.
  • The method caller can CATCH the exception in a TRY control structure and can access the exception object using INTO. The code executed in the TRY block is called the protected area.

To be complete, also the meaning of CLEANUP shall be shown:


CLASS cx_demo0 DEFINITION INHERITING FROM cx_static_check.

ENDCLASS.

CLASS cx_demo1 DEFINITION INHERITING FROM cx_static_check.

ENDCLASS.

START-OF-SELECTION.

  DATA(exception) = `cx_demo1`.

  cl_demo_input=>request( CHANGING field = exception ).

  DATA exc TYPE REF TO cx_static_check.

  exc = COND #( WHEN exception = `cx_demo0` THEN NEW cx_demo0( )

                ELSE NEW cx_demo1( ) ).

  TRY.

      TRY.

          RAISE EXCEPTION exc.

        CATCH cx_demo0.

          cl_demo_output=>write( ‘Catching cx_demo0’ ).

        CLEANUP.

          cl_demo_output=>write( ‘Cleanup’ ).

      ENDTRY.

    CATCH cx_demo1.

      cl_demo_output=>write( ‘Catching cx_demo1’ ).

  ENDTRY.

  cl_demo_output=>display( ).


In this example you can decide whether to raise an exception of class cx_demo0 or cx_demo1.cx_demo0 is handled in the inner, cx_demo1 is handled in the outer TRY block. In the latter case, before leaving the inner TRY block, the CLEANUP block is executed, offering the opportunity to cleanup the inner TRY block. Not shown here, is the INTO addition, that can be used for CLEANUP too.


Well known facts available since Release 6.10 (yawn). Maybe not so well known are RETRY and RESUME introduced with Release 7.02.


RETRY

With RETRY you exit a CATCH block and jump back to the TRY statement of the current TRY control structure in order to retry the full TRY block. Of course you have to take care, that the exception does not re-occurr  again and again, otherwise you end up in an endless loop.

CLASS cx_demo DEFINITION INHERITING FROM cx_static_check.

ENDCLASS.


CLASS cls DEFINITION.

  PUBLIC SECTION.

    CLASS-DATA flag TYPE abap_bool.

    CLASS-METHODS meth RAISING cx_demo.

ENDCLASS.


CLASS cls IMPLEMENTATION.

  METHOD meth.

    …

    IF flag = abap_false.

      cl_demo_output=>write( `Raising` ).

      RAISE EXCEPTION TYPE cx_demo.

    ENDIF.

    …

  ENDMETHOD.

ENDCLASS.

START-OF-SELECTION.

  TRY.

      cls=>meth( ).

    CATCH cx_demo.

      cl_demo_output=>write( `Handling` ).

      cls=>flag = abap_true.

      RETRY.

  ENDTRY.

  cl_demo_output=>display( ).


An exception is raised if some prerequisite (here a flag) is missing. During handling, the prerequisite is fullfilled and the execution of the protected area of the TRY block is repeated,


RESUME

With RESUME you you exit a CATCH block and resume processing after the statement that raised the exception. Prerequisites for that are:

  • The exception is resumable
  • The context of the exception is still available

CLASS cx_demo DEFINITION INHERITING FROM cx_static_check.

ENDCLASS.

CLASS cls DEFINITION.

  PUBLIC SECTION.

    CLASS-METHODS meth RAISING RESUMABLE(cx_demo).

ENDCLASS.

CLASS cls IMPLEMENTATION.

  METHOD meth.

    …

    cl_demo_output=>write( `Raising` ).

    RAISE RESUMABLE EXCEPTION TYPE cx_demo.

    cl_demo_output=>write( ‘Resuming …’ ).

    …

  ENDMETHOD.

ENDCLASS.

START-OF-SELECTION.

  TRY.

      cls=>meth( ).

    CATCH BEFORE UNWIND cx_demo.

      cl_demo_output=>write( `Handling` ).

      RESUME.

  ENDTRY.

  cl_demo_output=>display( ).

  • The property of being resumable is not part of an exception class but defined when raising an exception.
    • The addition RESUMABLE to RAISE raises an exception as a resumable exception.
    • The addition RESUMABLE to RAISING propagates a resumable exception (without, an resumable exception would lose this property during propagation).
  • The addition BEFORE UNWIND to CATCH takes care that the exception is caught before its context is deleted.

Maybe the information provided here can be useful for you.


For more infornation see Class-based Exceptions.





To report this post you need to login first.

10 Comments

You must be Logged on to comment or reply to a post.

  1. Suhas Saha

    I’ve been using resumable exceptions for quite sometime and i find them to be really useful.

    The demo program DEMO_CATCH_EXCEPTION really helped me to understand the behaviour of BEFORE UNWIND, RESUME et al.

    PS – I have not yet seen any std. SAP class using resumable exceptions though 😐

    (0) 
    1. Matthew Billingham

      I have a RESTful webservice I call where sometimes the first attempt at connection fails if the server has gone to sleep (the first attempt wakes it up, so it’s ready for the second). RETRY is great for these situations – making sure I only retry once, however.

      Can you share a use case for resumables though?

      (0) 
  2. Tapio Reisinger

    I use resumables when I do a loop over a table in the lower level method, e.g. table of documents which needs to be posted. If there is an error for one document, I raise an exception and catch it in upper level method, but still want to continue with the other documents.

    By the way, I would like to see the new T100 message handling from 7.50 in lower releases. Any possibility?

    (0) 
    1. Horst Keller Post author

      By the way, I would like to see the new T100 message handling from 7.50 in lower releases. Any possibility?


      Nope 😐

      (0) 
  3. Carol Vega

    TRY βˆ’ The TRY block contains the application coding whose exceptions are to be handled. This statement block is processed sequentially. It can contain further control structures and calls of procedures or other ABAP programs. It is followed by one or more catch blocks.

    CATCH βˆ’ A program catches an exception with an exception handler at the place in a program where you want to handle the problem. The CATCH keyword indicates the catching of an exception.

    DEMO_CATCH_EXCEPTION

    (0) 
  4. Michal Unger

    I am using resumable exceptions exactly as Tapio Reisinger does – for handling non-critical entries in a loop (the faulty record is logged and the remaining records can still be processed this way).

    However, sometimes we might face the situation when system exception (non-resumable by the nature) is raised during the loop processing – but we would still like to process the rest. This can be solved with the workaround I found by luck (which is probably a hack, I agree) seen in the code attached (CX_SY_ZERODIVIDE is turned to be resumable here).The trick is to raise another RESUMABLE exception within the CATCH block of non-resumable one.

    REPORT znon_resumable_exc_1_hacked.
    * Non-resumable cx_sy_zerodivide turned into resumable one via raising resumable lcx_1
    CLASS lcx_1 DEFINITION INHERITING FROM cx_static_check.
    ENDCLASS.                    "lcx_1 DEFINITION
    
    TYPES:
      BEGIN OF ty_st,
        a TYPE i,
        b TYPE i,
        c TYPE i,
      END OF ty_st,
      ty_tt TYPE STANDARD TABLE OF ty_st WITH EMPTY KEY.
    
    START-OF-SELECTION.
      DATA(lt) = VALUE ty_tt( a = 10 ( b = 5 )
                                     ( b = 0 )
                                     ( b = 2 ) ).
      TRY.
          LOOP AT lt ASSIGNING FIELD-SYMBOL(<ls>).
            <ls>-c = <ls>-a / <ls>-b.
          ENDLOOP.
        CATCH BEFORE UNWIND cx_sy_zerodivide .
          TRY.
              RAISE RESUMABLE EXCEPTION TYPE lcx_1.
            CATCH BEFORE UNWIND lcx_1.
              RESUME.
          ENDTRY.
          RESUME.
      ENDTRY.
    
      LOOP AT lt ASSIGNING <ls>.
        IF <ls>-c IS NOT INITIAL.
          WRITE:/ |{ <ls>-a }/{ <ls>-b }={ <ls>-c }|.
        ELSE.
          WRITE:/ |{ <ls>-a }/{ <ls>-b }=# Zero divisor on line { sy-tabix }|.
        ENDIF.
      ENDLOOP.

    Not only this works across single or multiple (nested) TRY .. ENDTRY blocks in program unit but also with exceptions being propagated from methods, both of local and global classes.

    Example for local class method:

    REPORT znon_resumable_exc_2_hacked.
    * Non-resumable cx_sy_zerodivide was turned into resumable via raising resumable lcx_1
    CLASS lcx_1 DEFINITION INHERITING FROM cx_static_check.
    ENDCLASS.                    "lcx_1 DEFINITION
    
    CLASS lcl_1 DEFINITION.
      PUBLIC SECTION.
    * To define RESUMABLE exception to be propagated, it should normally be like this:
    * CLASS-METHODS: loop_and_divide RAISING RESUMABLE(cx_sy_zerodivide).
    * Using the trick, this works, too:
      CLASS-METHODS: loop_and_divide RAISING cx_sy_zerodivide.
    ENDCLASS.
    
    CLASS lcl_1 IMPLEMENTATION.
      METHOD loop_and_divide.
        TYPES:
          BEGIN OF ty_st,
            a TYPE i,
            b TYPE i,
            c TYPE i,
          END OF ty_st,
          ty_tt TYPE STANDARD TABLE OF ty_st WITH EMPTY KEY.
    
        DATA(lt) = VALUE ty_tt( a = 10 ( b = 5 )
                                       ( b = 0 )
                                       ( b = 2 ) ).
    
        LOOP AT lt ASSIGNING FIELD-SYMBOL(<ls>).
          <ls>-c = <ls>-a / <ls>-b.
        ENDLOOP.
        LOOP AT lt ASSIGNING <ls>.
          IF <ls>-c IS NOT INITIAL.
            WRITE:/ |{ <ls>-a }/{ <ls>-b }={ <ls>-c }|.
          ELSE.
            WRITE:/ |{ <ls>-a }/{ <ls>-b }=# Zero divisor on line { sy-tabix }|.
          ENDIF.
        ENDLOOP.
      ENDMETHOD.
    ENDCLASS.
    
    START-OF-SELECTION.
      TRY.
          lcl_1=>loop_and_divide( ).
        CATCH BEFORE UNWIND cx_sy_zerodivide .
          TRY.
              RAISE RESUMABLE EXCEPTION TYPE lcx_1.
            CATCH BEFORE UNWIND lcx_1 INTO DATA(lx).
              RESUME.
          ENDTRY.
          RESUME.
      ENDTRY.

    Example with simple global class method (defined exactly the same way as for the local class example above, but in SE24):

    REPORT znon_resumable_exc_3_hacked.
    * Non-resumable cx_sy_zerodivide was turned into resumable via raising resumable lcx_1
    CLASS lcx_1 DEFINITION INHERITING FROM cx_static_check.
    ENDCLASS.                    "lcx_1 DEFINITION
    
    START-OF-SELECTION.
      TRY.
          zcl_zerodivide_test=>loop_and_divide( ).
        CATCH BEFORE UNWIND cx_sy_zerodivide .
          TRY.
              RAISE RESUMABLE EXCEPTION TYPE lcx_1.
            CATCH BEFORE UNWIND lcx_1 INTO DATA(lx).
              RESUME.
          ENDTRY.
          RESUME.
      ENDTRY.

    The feature was found some time ago already but still β€˜works’ in quite new 7.50 SP6.

    I would like to hear whether this is a bug or intended hidden functionality?

    (0) 

Leave a Reply