TRY and RETRY, CATCH and RESUME
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.
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 π
DEMO_CATCH_EXCEPTION π
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?
I had discussed this here: Where do i use RESUMABLE EXCEPTION?
Yep, as stated there, The reason for resumable exceptions is to give the calling program the control if the error is fatal or not.
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?
Nope π
Good one.
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.
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:
Example with simple global class method (defined exactly the same way as for the local class example above, but in SE24):
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?