Skip to Content
Author's profile photo Raghu Govindarajan

Handling old-style errors via class based exceptions

SAP is in an interesting spot with ABAP. There are a lot of newer and better ways to things, but a lot of old things also work. Trying to get the benefits of the new ways while using existing business logic which was written the old way always throws up some fun.

In this particular case, I will show you the solution that I have come up with to handle the old style exceptions thrown by a function module call. This method is completely generic and should work with any function module call that throws classic exceptions.

Setup

The new exception class

Create a new exception class that you will use to “translate” these messages from one format to another. In this class, you have to add 5 new attributes to carry your Message Type and Message Variables.

If you are doing this with the Eclipse environment, this is what you would declare.

CLASS zcx_my_exception DEFINITION
  PUBLIC
  INHERITING FROM cx_static_check
  FINAL
  CREATE PUBLIC .

  PUBLIC SECTION.

    INTERFACES if_t100_message .

    "Start of addition
    DATA msgty TYPE symsgty .
    DATA text1 TYPE sylisel .
    DATA text2 TYPE sylisel .
    DATA text3 TYPE sylisel .
    DATA text4 TYPE sylisel .
    "End of addition

    METHODS constructor
      IMPORTING
        !textid   LIKE if_t100_message=>t100key OPTIONAL
        !previous LIKE previous OPTIONAL
        !msgty    TYPE symsgty OPTIONAL
        !text1    TYPE sylisel OPTIONAL
        !text2    TYPE sylisel OPTIONAL
        !text3    TYPE sylisel OPTIONAL
        !text4    TYPE sylisel OPTIONAL .
  PROTECTED SECTION.
  PRIVATE SECTION.
ENDCLASS.

 

The calling Methods

In this code sample, I have the key pieces of code that you will need. Please don’t pay attention to the fact that the variables and import – export parameters are not declared.

When declaring the class, all the methods have to be declared with the RAISING zcx_my_exception. This ensures that any exception that is raised is passed to the next level.

CLASS lcl DEFINITION.
  PUBLIC SECTION.
    METHODS get_master_data
      RAISING zcx_my_exception.

    METHODS call_function
      RAISING zcx_my_exception.

    METHODS call_bapi
      RAISING zcx_my_exception.

ENDCLASS.

Standard Function Modules

First the standard function call. I have an additional method GET_MASTER_DATA( ) which is just an additional layer to show how exceptions can be handled at the highest level. For those of you who haven’t used class based exceptions in ABAP before, this is to give you a glimpse of the power and simplicity of using this over the traditional method of passing and handling the exception at every level.

CLASS lcl IMPLEMENTATION.
  METHOD get_master_data.
  
    "Prep the material number, etc

    me->call_function( ).

    "Post call routine - Do something with data
    me->call_bapi( ).
    
  ENDMETHOD.

 

  METHOD call_function.
  
    CALL FUNCTION 'MATERIAL_READ'
      EXPORTING
        schluessel           = key_fields    " Material master key fields
      IMPORTING
        matdaten             = view_tab    " Material master view
        return               = return
        matper               = matper
      EXCEPTIONS
        account_not_found    = 1
        batch_not_found      = 2
        forecast_not_found   = 3
        lock_on_account      = 4
        lock_on_material     = 5
        lock_on_plant        = 6
        lock_on_sales        = 7
        lock_on_sloc         = 8
        lock_on_batch        = 9
        lock_system_error    = 10
        material_not_found   = 11
        plant_not_found      = 12
        sales_not_found      = 13
        sloc_not_found       = 14
        slocnumber_not_found = 15
        sloctype_not_found   = 16
        text_not_found       = 17
        unit_not_found       = 18
        invalid_mch1_matnr   = 19
        invalid_mtcom        = 20
        sa_material          = 21
        wv_material          = 22
        waart_error          = 23
        t134m_not_found      = 24
        error_message        = 25
        OTHERS               = 26.

    IF sy-subrc <> 0.
      RAISE EXCEPTION TYPE zcx_my_exception
        EXPORTING
          textid = VALUE scx_t100key( msgid = sy-msgid
                                      msgno = sy-msgno
                                      attr1 = 'TEXT1'
                                      attr2 = 'TEXT2'
                                      attr3 = 'TEXT3'
                                      attr4 = 'TEXT4' )
          msgty  = sy-msgty
          text1  = CONV sylisel( sy-msgv1 )
          text2  = CONV sylisel( sy-msgv2 )
          text3  = CONV sylisel( sy-msgv3 )
          text4  = CONV sylisel( sy-msgv4 ).
    ENDIF.

  ENDMETHOD.

A couple of points of note here. Always declare the extra catch all exception ERROR_MESSAGE (in this case = 25). This is a safety in case the person who programmed the function module raised an exception that was not declared. I have had crashes when I forgot to do this.

The other is the fact that I am also passing the MSGTY (Message Type). Even though the new class based exceptions are “Typeless”, because you raise different ones in different circumstances, I sometimes find it useful to have this field around. This is a personal preference, because you can certainly make the argument that all exceptions out of a function module are errors.

Using a BAPI

is similar, but instead of the SYST fields, you will be using the data from the Bapi return table.

  METHOD call_bapi.
    DATA:
      bapiret_tab TYPE STANDARD TABLE OF bapiret2.

    CALL FUNCTION 'BAPI_PRODORD_CREATE'
      EXPORTING
        orderdata    = orderdata    " Transfer Structure for Creating Production Orders
      IMPORTING
        return       = bapiret_tab    " Return Parameters
        order_number = e_order_number.  " Production order number

    LOOP AT bapiret_tab INTO DATA(bapiret) WHERE type = 'E'.
      RAISE EXCEPTION TYPE zcx_my_exception
        EXPORTING
          textid = VALUE scx_t100key( msgid = bapiret-id
                                      msgno = bapiret-number
                                      attr1 = 'TEXT1'
                                      attr2 = 'TEXT2'
                                      attr3 = 'TEXT3'
                                      attr4 = 'TEXT4' )
          msgty  = bapiret-type
          text1  = CONV sylisel( bapiret-message_v1 )
          text2  = CONV sylisel( bapiret-message_v2 )
          text3  = CONV sylisel( bapiret-message_v3 )
          text4  = CONV sylisel( bapiret-message_v4 ).
    ENDLOOP.

  ENDMETHOD.

ENDCLASS.

The LOOP on the bapiret_tab is just one way of filtering the table. You can of course use the READ TABLE or table expressions (bapiret = bapiret_tab[ type = ‘E’ ] ) to achieve the same. This is where the use of the MSGTY gets interesting because a lot of BAPI’s do send back the full gamut of Warnings and Success messages too. Some time in the future, I would like to experiment with making this RAISE a RESUMABLE one, and using that to log the Success and Warning messages.

Using the exceptions

This is the easy part! Just like any other TRY… CATCH.

  TRY.
      DATA(obj) = NEW lcl( ).
      obj->get_master_data( ).

    CATCH zcx_my_exception INTO DATA(ocx_my).
      " Do some clean up work
      " ....
      
      " Show the user the error message
      MESSAGE ID ocx_my->if_t100_message~t100key-msgid
              TYPE ocx_my->msgty
              NUMBER ocx_my->if_t100_message~t100key-msgno
         WITH ocx_my->text1
              ocx_my->text2
              ocx_my->text3
              ocx_my->text4.
  ENDTRY.

The only tricky part is how you get the MSGID and MSGNO from the exception object that has been caught – you have to go through the interface for accessing the T100 texts. So, that is a couple of layers deep.

Enjoy your errors!

Inspiration and References

This is in response to Mathew Billingham’s question of a few years ago… Handling general errors via class based exceptions I just had a similar issue and just like him I realized that the exception object carries all the information that you need.

I have also had some inspiration from Horst Keller’s post ABAP News for Release 7.50 – Converting Messages into Exceptions | SAP Blogs and also from Jörg Krause’s Blog The hassle with function module calls – part 2 | SAP Blogs and the subsequent discussions.

 

 

Assigned Tags

      7 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Andre Schüßler
      Andre Schüßler

      When a function module raises a classic exception with the "raise" statement, the sy-msg* fields are not set. Your code will not work properly.

      These fields are only set by statement "message ... raising ...".

       

      Author's profile photo Raghu Govindarajan
      Raghu Govindarajan
      Blog Post Author

      That is correct. The vast majority of the SAP provided function modules do use the "message... raising...". Based on the number of records in T100, that is over half a million times that you would not have to re-declare the messages.

      If just the RAISE statement was used, then there is no message associated with the error and if you want someone other than a programmer to understand the error, you will be creating a human readable message. Another story for another day 🙂

      Author's profile photo Christian Drumm
      Christian Drumm

      Hi Raguh,

      in AiE there are also templates available to create the necessary exception class. See the following blog by Thomas Fiedler for examples how to use the templates:

      https://blogs.sap.com/2013/05/14/creating-exception-classes-using-code-templates/

      Christian

       

      Author's profile photo Raghu Govindarajan
      Raghu Govindarajan
      Blog Post Author

       

      Christian,

      Thomas’ blog shows you how to use the template to quickly enter a new Text ID in an exception class. That is used when you want to create your own message or explicitly raise an error with an associated message. In those cases, you will have to explicitly raise the error with that text-id. For example…

      IF condition.
        RAISE EXCEPTION TYPE zcx_my_exception
          EXPORTING
            textid = zcx_my_exception=>my_specific_textid.
      ENDIF.

      The difference is that in my blog, I am giving you a method to do this generically without the need to re-declare a text-id for every message that can possibly be thrown by a function module.

       

      Author's profile photo Former Member
      Former Member

      We use the structure SCX_T100KEY to store the message attributes –

          CALL FUNCTION 'SPBT_INITIALIZE'
            EXPORTING
              group_name                     = me->ms_parallel_settings-server_group
            IMPORTING
              free_pbt_wps                   = lv_free_pbt_wps
            EXCEPTIONS
              invalid_group_name             = 1
              internal_error                 = 2
              pbt_env_already_initialized    = 3
              currently_no_resources_avail   = 4
              no_pbt_resources_found         = 5
              cant_init_different_pbt_groups = 6
              OTHERS                         = 7.
          IF sy-subrc <> 0 AND sy-subrc <> 3.
      
            RAISE EXCEPTION TYPE zcx_parallel_proc
              EXPORTING
                textid = zcl_dev_appl_log=>get_msg_t100key( ).
      
          ENDIF.

      The method GET_MSG_T100KEY looks like this –

      METHOD GET_MSG_T100KEY.
        re_s_t100key-msgid = sy-msgid.
        re_s_t100key-msgno = sy-msgno.
        re_s_t100key-attr1 = sy-msgv1.
        re_s_t100key-attr2 = sy-msgv2.
        re_s_t100key-attr3 = sy-msgv3.
        re_s_t100key-attr4 = sy-msgv4.
      ENDMETHOD.

      I am not sure why would you like to have new set of attributes to save the message details when you have the IF_T100_MESSAGE~T100KEY attribute.

      I am not a fan of having message type in an exception object. IMHO “how” to handle the exception should rest with the handler (who CATCHes the exception) i.e., it should decide if the exception is supposed to be of type Error or an Information.

      – Suhas

      PS – The code was written pre ABAP740 hence the helper method. Post ABAP740 one can simply use the VALUE operator

      Author's profile photo Raghu Govindarajan
      Raghu Govindarajan
      Blog Post Author

      Thanks for the suggestion. It seems like a good idea. I wish there was a way to get people’s attention like in the old SCN – I would like Jörg Krause and Horst Keller to weigh in too. If you look at their posts that I have linked to at the bottom of my original post, they have also done the same in linking the ATTRx field to link the actual attribute name. I guess I didn't question that like you did, and i probably will use a variation of your method in future.

      All I can say is why I add the message attributes. I tend use the same error class for an entire development or process (something that makes logical sense at the time). This way, I can combine using class based messages the way they were intended and also this conversion from old to new behavior using the same exception class – and therefore the same try..catch doing all my error handling for a screen.

      The message type, I knew would be controversial and I did say so in the post that it was my personal preference. Here is a little more about why I like it. It gives me an automatic way to filter messages and handle them differently. Here is an over-simplified example of what I am talking about.

      CATCH cx_my_error.
        IF my_error->msgtyp = 'S'.
          "Success, update log, move to next screen in process
        ELSE.
          "Display Error
          "Clean up
          "Stop on current process
        ENDIF.
      

      If I didn’t have the msgtyp, I would have to do something like this instead…

      CATCH cx_my_error.
        IF my_error->msgid = 'ZZ' and my_error->msgno = '010'. "Material batch saved
          do_success = abap_true.
        ELSEIF my_error->msgid = 'ZZ' and my_error->msgno = '011'. "Material saved
          do_success = abap_true.
        ELSEIF my_error->msgid = 'ZZ' and my_error->msgno = '012'. "Nothing needed to change 
          do_success = abap_true.
        ELSE.
          do_success = abap_false.
        ENDIF.
      
        IF do_success = abap_true.
          "Success, update log, move to next screen in process
        ELSE.
          "Display Error
          "Clean up
          "Stop on current process
        ENDIF.
      

      As you can see, I would need to know every combination of success message that can be thrown at me and their Message Ids and Numbers.

      Author's profile photo Joachim Rees
      Joachim Rees

      Hi Raghu,

      thanks for sharing, I might use a concept like this at some time.

      extra catch all exception ERROR_MESSAGE (in this case = 25). This is a safety in case the person who programmed the function module raised an exception that was not declared.

      You're right that it's usually  a good idea to include ERROR_MESSAGE as well (i usually use ERROR_MESSAGE = 88, Others = 99), but I'm not sure you have explained the reason correctly:

      ERROR_MESSAGE comes into effect, when during the execution of the Function-Module a message Typ E (without RAISING) is given. (It has some more effects, F1 help on Call Function explains it nicely).

       

      I also like how you reference the blogs inspiring you in the end -> might be a good "further reading"!

       

      best

      Joachim