logo

Are you need IT Support Engineer? Free Consultant

New custom Application Job features and using Noti…

  • By sujay
  • 09/06/2026
  • 26 Views

A year back I introduced the use of the Application Job framework for background scheduling, instead of se38 reports, to be clean core compliant. A big part of that blog post still applies today, but we also had a few changes with the 2025 release, which we will look at in this blog post. But for a general look at the framework and also how does it work/look like when considering background scheduling and Fiori, you may want to have a look at the previous blog: How to do background scheduling with clean core…

I also would like to mention at the start that what follows below is adjusted to SAP S/4HANA PCE and on-premise versions. However, the first part can also be considered in Public Cloud, potentially with a few minor changes here and there. The notification part showcased here is not applicable for public cloud, however.

 

A quick summary of the changes if you do not have time to check the step-by-step below.

  • We now only use one ABAP class interface for our application job class: if_apj_rt_run
    • Optionally there is also ABAP class interface if_apj_dt_defaults, if you want to write some logic to determine certain default values (if you do not need logic, default values can directly be assigned to the class attributes that act as parameters, see next point below)
  • Class attributes in our application job class are now the parameters for the job
  • Application Job Catalog entry maintenance is updated in ADT and allows for more flexibility on the parameters, such as assigning CDS based value-helps (no more need for old-school DDIC)
  • The application job can be integrated with Fiori Launchpad notifications via Notification Exit on the Application Job Catalog

 

I am using the same bookstore example as from my previous blog. For details on the data structure of that example, please check the previous post as linked above.

General SAP Help for the newer version: Implementing the Business Logic | SAP Help Portal

2.1 Application job class

First step as before is to create our application job class, which is a regular ABAP class (in ABAP language version ABAP for Cloud Development to be clean core compliant) which uses interface if_apj_rt_run (and optionally if_apj_dt_defaults for determining default values via code).

class zcl_apj_book_reorder definition
  public
  final
  create public .

  public section.
    interfaces if_apj_rt_run.
    " optional: only needed IF you need logic to fill the default values.
    " otherwise, simply use VALUE statement in the class attributes
    interfaces if_apj_dt_defaults.
endclass.

2.2 Job parameters

The job parameters are no longer defined in a method but directly in the public section of the class as DATA attributes:

  public section.
    types:
      ty_tt_bookid_range   type range of zbook_id.
    data:
      " CLASS ATTRIBUTES as Job Parameters ------------------------------------
      " The length of the attribute name is limited to 38 characters!
      " When a parameter is added to a job catalog entry, the parameter title is 
      " taken from the description text of the class attribute.
      " If the selection field allows the input of multiple values or a value range, 
      " the class attribute must be defined as ranges table.
      "! 

Bookstore Id

p_bookstoreid type zbookstore_id value '001', " this parameter is defaulted to 001 "!

Book Id(s)

s_bookid type ty_tt_bookid_range, "!

Reorder Quantity

p_reorder_qty type i, "!

Simulation Only

p_is_simulation type abap_bool .

2.2.1 Fill default values via logic (optional)

If you need logic to pre-fill default values for your job parameters, you can optionally add class interface if_apj_dt_defaults which will give you method if_apj_dt_defaults~fill_attribute_defaults. In the below example, I default the quantity of the book reordering depending on the current month: during the most popular reading months we default 20 and on all other months, 10.

  method if_apj_dt_defaults~fill_attribute_defaults.
    " check for "high season" which is during summer months June, July, April
    final(lv_today) = cl_abap_context_info=>get_system_date( ).
    final(lv_month) = lv_today+04(2).
    p_reorder_qty = cond #(
      when lv_month="06" or lv_month="07" or lv_month="08" then 20
      else 10 ).
  endmethod.

2.3 Job execution/logic

The job execution is coded into method if_apj_rt_run~execute. For the actual execution, nothing changed in my code from my previous example (with the exception of not having to first “get my job parameters”, since they are already there as my attributes and can be directly accessed).
But, what I left out in my previous example was to add failure to the job, for example if the job was started with a bookstore id that does not exist. That shouldn't just “end” the job and do nothing, making the job appear “Finished” (and successful) in monitoring, but now we want to throw an error. Here is how this could be achieved within the execute method:

    " get bookstore for which reorder was requested
    select single bookstoreid from zi_bookstore
      where bookstoreid = @p_bookstoreid
      into @final(lv_bookstore_id_check).
    if sy-subrc <> 0.
      " fail job if bookstore doesn't exit
      add_text_to_log( iv_text     = |Bookstore with id { p_bookstoreid } not found|
                       iv_severity = if_bali_constants=>c_severity_error ).
      raise exception type cx_apj_rt_content 
        message id 'ZBOOKSTORE' number 003 with p_bookstoreid.
    endif.

Very simple code: I check my class attribute p_bookstoreid which is my job parameter for the bookstore id and select from my bookstores basic interface CDS view entity. If the bookstore was not found, we end job execution by raising cx_apj_rt_content exception with my own message class and id. How this looks like in job monitoring we will see when we test this whole example.

This concludes our application job class.

2.4 Application Job Catalog Entry

Next step is to create the Application Job Catalog Entry in ADT (New -> Other ABAP Object -> Search for Application Job and double click on Application Job Catalog Entry). Once created, the first thing we have to do is to give our application job class we created in step 2.1. Next, we may give some Exit Classes but we skip this for now.

Here now we can create the “screen/layout” for the job parameters by using Sections and Groups. Here my example:

Two main sections (Bookstore and Simulation) and three groups: 2 are in Bookstore, 1 in Simulation. Lastly under parameters, we assign them to the group they should appear under. Here is how it will finally look like in Fiori:

Mlauber_1-1780916974287.Png

2.5 Job Catalog Template

Next, we need an application job template to be able to create application jobs in the SAP standard Fiori app Application Jobs  (F1240). Create this in ADT (New -> Other ABAP Object -> Search for Application Job and double click on Application Job Template).

Here we enter the Application Job Catalog Entry we just created in step 2.4. If we want, we can default values also here or simply keep the ones already defined in the Application Job ABAP Class.

That's it, from now on we can schedule our custom application job in the standard Fiori app.

2.6 Integration with Fiori Launchpad Notifications

For critical jobs, we may want some extra handling and notifications to users. With a Notification Exit on the Application Job Catalog Entry we can achieve just that.

2.6.1 Custom Notification Channel

First, we need a custom Notification Channel we can use to create our custom notifications to the Fiori Launchpad. To use notifications in general, your admin needs to do certain one-off configuration, you may refer here: Notification Channel | SAP Help Portal.

2.6.2 Notification provider class

Assuming that notifications are fully configured, to add a custom notification channel we once again need to create an ABAP Class, this time in ABAP language version Standard ABAP, as the interface /iwngw/if_notif_provider is not released. However, this interface is classified as “Classic API” on the Cloudification Repository Viewer, so we are still clean core compliant, level B. Now this class gives us several methods to fill and they may not seem straightforward at first. Let's go through one-by-one.

2.6.2.1 get_notification_type

Here you simply decide “what kind of notifications” this specific notification provider class can and does handle. I'll give an example with 2 notification types: one for the reorder application job we've been working on so far. And one when a new book gets added to the bookstore, and this book needs approval, which means we also create two action buttons. This is how the code could look like:

    data ls_naction like line of et_notification_action.
    clear: es_notification_type, et_notification_action.

    if ( iv_type_key = zcl_cust_notif_provider=>gc_type_book_reorder ).
      es_notification_type-version  = iv_type_version.
      es_notification_type-type_key = iv_type_key.

    elseif ( iv_type_key = zcl_cust_notif_provider=>gc_type_new_book ).
      es_notification_type-version  = iv_type_version.
      es_notification_type-type_key = iv_type_key.
      " actions
      ls_naction-action_key = gc_action_approve.
      ls_naction-nature = /iwngw/if_notif_provider=>gcs_action_natures-positive.
      append ls_naction to et_notification_action.
      ls_naction-action_key = gc_action_reject.
      ls_naction-nature = /iwngw/if_notif_provider=>gcs_action_natures-negative.
      append ls_naction to et_notification_action.
    else.
      raise exception type /iwngw/cx_notif_provider.
    endif.

The notification type key is again something you decide yourself. In my case I put them as public constants so the same type key can be used outside of this provider class (for example in my notification exit class for the application job).

    constants:
      gc_type_book_reorder type /iwngw/notification_type_key    value 'BookstoreBookReorder',
      gc_type_new_book     type /iwngw/notification_type_key    value 'BookstoreNewBook',

2.6.2.2 get_notification_parameters

This method is used to define “parameters” per notification type (that you decided on in get_notification_type) that can be sent into the creation of a certain notification, that act as placeholders in the notification message and then get replaced by the actual values for these parameters, when the notification is created. Example:

    data ls_parameter type /iwngw/s_bep_notif_parameter.

    ls_parameter = value #( name         = gc_para_bookstoreid
                            type         = /iwngw/if_notif_provider=>gcs_parameter_types-type_string
                            is_sensitive = abap_false ).
      append ls_parameter to et_parameter.
    if ( iv_type_key = zcl_cust_notif_provider=>gc_type_book_reorder ).
      ls_parameter = value #( name         = gc_para_jobname
                              type         = /iwngw/if_notif_provider=>gcs_parameter_types-type_string
                              is_sensitive = abap_false ).
      append ls_parameter to et_parameter.
    elseif ( iv_type_key = zcl_cust_notif_provider=>gc_type_new_book ).
      ls_parameter = value #( name         = gc_para_bookid
                              type         = /iwngw/if_notif_provider=>gcs_parameter_types-type_string
                              is_sensitive = abap_false ).
      append ls_parameter to et_parameter.
    else.
      raise exception type /iwngw/cx_notif_provider.
    endif.

I create a parameter for the bookstore id (again using constants in my class, as for the type key) for both notification types, and then for the new book added into the bookstore I have a parameter for the book id, whilst for the reorder application job, I add the job name as a parameter.

2.6.2.3 get_notification_type_text

Here we define the notification text and add our parameter placeholders. This method is also used to give text to the action buttons we have defined in get_notification_type:

    if ( iv_type_key = zcl_cust_notif_provider=>gc_type_book_reorder ).
      es_type_text = value #( template_public="Reordering of books"
                              template_sensitive="Reordering books for bookstore {&1} with job {&2}" ).
      replace '&1' in es_type_text-template_sensitive with gc_para_bookstoreid.
      replace '&2' in es_type_text-template_sensitive with gc_para_jobname.

    elseif ( iv_type_key = zcl_cust_notif_provider=>gc_type_new_book ).
      es_type_text = value #( template_public="A new book in your bookstore requires approval"
                              template_grouped    = 'New books in your bookstore require approval'
                              template_sensitive="Book {&1} added to bookstore {&2} requires approval" ).
      replace '&1' in es_type_text-template_sensitive with gc_para_bookid.
      replace '&2' in es_type_text-template_sensitive with gc_para_bookstoreid.

      append initial line to et_action_text assigning field-symbol().
       = value #( action_key           = gc_action_approve
                             display_text="Approve Book"
                             display_text_grouped = 'Approve All' ).
      append initial line to et_action_text assigning .
       = value #( action_key           = gc_action_reject
                             display_text="Reject Book"
                             display_text_grouped = 'Reject All' ).
    else.
      raise exception type /iwngw/cx_notif_provider.
    endif.

2.6.2.4 handle_action / handle_bulk_action

At this point we would be ready to register our notification provider for a new custom notification channel, if we had no actions. But, because we have approve and decline actions for the event of a New Book, we also need to implement the handle_action method (and potentially handle_bulk_action, if you allow the notifications to be grouped in method get_notification_type, which I did not). Now to disclose something rather strange / missing functionality of this: the importing parameters into this method do not include the parameters (values) we had filled when creating a notification. So at this point, I don't know the bookstore id and neither the book id. So I can't really code my book approval without that. As of right now, there seem to be two main workarounds for this gap:

  1. When creating the notification id, you could include the parameter values into the id itself, but be careful as I believe the id needs to be unique (fully unique or per day, I'm honestly not sure). Once we are in the handle_action method, we can then split the notification id again and get our parameter values. However this kind of coding could be considered unsecure.
  2. Create a singleton object in your notification provider class and save the parameter values into that singleton. This means however you need to remember to get the instance and fill the values, at the same time as you enter the parameter values when you create the notification. And then in handle_action you fetch the same singleton and have the parameter values that way.

Neither are a great solution and I have no explanation as to why this fairly big gap exists (considering that the notification provider is classified “Classic API”, I am hoping something newer will be available soon). Now because our application job scenario has no actions, I will leave this here. You can also write a comment on this post, if my explanation above on how to potentially do this is not clear.

2.6.3 Register and activate Notification provider 

To register your provider, go to transaction /IWNGW/BEP_NPREG and add a new entry with your new provider with the provider class we just  created.

To activate your custom notification channel, go to transaction /IWNGW/VB_REG_P and add a new entry with your notification provider that you just registered, and mark the checkbox Is Active.

That's it, from now we can send notification to the Fiori Launchpad with our custom notification channel. Let's do just that by using the Notification Exit on your Application Job Catalog Entry.

2.6.4 Create Notification Exit for Application Job

For semi-automatic hooks for our custom job execution with notifications, we can another ABAP class for as the notification exit class for our job. Once again this needs to be in Standard ABAP as we will be calling our notification class provider, which is built with Classic APIs. So once again we are still clean core compliant level B. This class will use interface if_apj_rt_job_notif_exit. This gives us two methods, one of which is called automatically if a job goes into fail state, and the other we can manually call from our application job execution class, if we want. Let's have a look at both.

2.6.4.1 if_apj_rt_job_notif_exit~notify_jt_end (called automatically if a job gets status failed or cancelled)

If my job fails, I want to send a mail to the manager (me 😁) so that this cannot get missed and action can be taken. So I'm writing a very simple piece of code for that:

    data:
      lv_mail_to type edi_email_address_gfn value 'm.lauber@sap.com',
      lv_content type string.

    lv_content =
      |

Book reorder job error

Job { is_job_info-job_name } failed.| & | Job step: { is_job_info-job_step }, step status: { iv_step_status }, job status:| & | { iv_job_status }

| ##NO_TEXT. try. data(lo_mail) = cl_bcs_mail_message=>create_instance( ). lo_mail->set_sender( 'demo.sender@demo.com' ). lo_mail->add_recipient( conv #( lv_mail_to ) ). lo_mail->set_subject( 'Bookstore RAP project' ). lo_mail->set_main( cl_bcs_mail_textpart=>create_instance( iv_content = lv_content iv_content_type="text/html" ) ). lo_mail->send( ). " importing et_status = data(lt_status) catch cx_bcs_mail into data(lo_bcs_mail_err). data(lv_msg_longtext) = lo_bcs_mail_err->get_longtext( ). endtry.

For simpleness and demo sake, I do not handle the exception or check mail-send status, which of course should be done in a real scenario.

2.6.4.2. if_apj_rt_job_notif_exit~notify_jt_start (code to run at job start, needs to be called manually)

For the start of the job I'm now going to make use of my custom notification channel. When the reordering of books job starts, I will simply inform the manager (still me) that the job has started so that I can know that new books are being purchased into the store. To create a notification with my notification channel, I need to use API /iwngw/cl_notification_api (again not released but classified as Classic API). Please note the whole method is in a try-catch-endtry block and I'll highlight which piece of code requires which catch. So let's take this slow step by step.

First, I need my job's parameter, so that I can fill the notifications parameter (which were bookstore id and job name):

data(lt_job_param) = cl_apj_rt_api=>get_job_param_values(
                               iv_jobname    = is_job_info-job_name
                               iv_jobcount   = is_job_info-job_count ).

This part of the code is ABAP Cloud compliant level A (cl_apj_rt_api is released). This call requires us to catch cx_apj_rt.

Next, we start filling in info for our notification:

  data:
    ls_notif          type /iwngw/if_notif_provider=>ty_s_notification.

  " prepare new notification
  ls_notif = value #( 
    id                       = cl_uuid_factory=>create_system_uuid( )->create_uuid_x16( )
    type_key                 = zcl_cust_notif_provider=>gc_type_book_reorder
    type_version             = '1'
    priority                 = /iwngw/if_notif_provider=>gcs_priorities-neutral
    actor_id                 = ''
    actor_type=""
    actor_display_text=""
    recipients               = value #( ( id = 'MANAGER' ) ) " for demo, hardcode myself
    " these are just for navigation showcasing, makes no sense in the bookstore scenario
    navigation_target_object="Material"
    navigation_target_action = 'displayStockMultipleMaterials'
    navigation_parameters    = value #( ( name="Material" value="73" ) )
  ).
  • This code needs to be Standard ABAP For line 2 and 9, because both are using Classic API. Everything else is ABAP Cloud compliant.
  • I am using the UUID factory (released) to generate a UUID for the notification id. Here, as mentioned before, instead I could put my parameter values into the id, if I was going for that approach.
  • I am also here using the constant of my custom notification provider class to give the correct notification type, which is the book reorder job.
  • I am not using any “actor” settings
  • The recipient is hardcoded here, which of course should not be the case for real cases. More than one user can be entered
  • The navigation fields I used here is to showcase that we can navigate from a notification in the Fiori Launchpad to an app. In my bookstore scenario this makes no sense, so this purely for demo purpose

Next, we will fill our job parameter values into the corresponding ones expected by the notification. First we retrieve all the parameters that the notification provider expects for our notification type. Then we us the parameters already read from the job and fill the values.

    " get parameters of this notification type
    zcl_cust_notif_provider=>/iwngw/if_notif_provider~get_notification_parameters(
      exporting
        iv_notification_id = ls_notif-id
        iv_type_key        = ls_notif-type_key
        iv_type_version    = ls_notif-type_version
        iv_language="E"
      importing
        et_parameter       =  lt_param
    ).
    " get bookstore id from job parameters
    read table lt_job_param into data(ls_job_param)
      with key tech_name="P_BOOKSTOREID".
    if sy-subrc = 0.
      data(ls_job_param_value) = ls_job_param-t_value[ 1 ].
    endif.
    "  set notification parameter values
    loop at lt_param assigning field-symbol().
      -value = cond #(
        when -name = zcl_cust_notif_provider=>gc_para_jobname then is_job_info-job_name
        when -name = zcl_cust_notif_provider=>gc_para_bookstoreid then ls_job_param_value-low
        else '' ).
    endloop.

In this block we are catching /iwngw/cx_notif_provider from getting notification parameters.

Last step is to add the parameters to our notification structure and finally to create the notification:

    " add parameters to notiication
    ls_notif-parameters = value #( ( language="E"
                                     parameters = lt_param ) ).
    " set and create notification
    append ls_notif to lt_notif.
    /iwngw/cl_notification_api=>create_notifications(
      exporting
        iv_provider_id  = gc_provider_id
        it_notification = lt_notif ).

 In this block we are catching /iwngw/cx_notification_api for creating the notification.

That's all. Our class is ready.

2.6.5 Add Notification Exit to Application Job Catalog Entry

Very simple, we open our Application Job Catalog Entry and give our ABAP Class under Notification Exit:

Mlauber_0-1780993985972.Png

As mentioned before, this will only automatically call the “end” method, if the job gets status failed or cancelled. To call the “start” method, I have to do it manually. This is of course in the Application Job class with the Execute Method. Let's do that:

  types:
      " new type for having notification exit info in one structure
      begin of ty_notif_api,
        notif_api     type ref to zcl_apj_book_reorder_notif,
        job_info      type if_apj_rt_job_notif_exit=>ts_job_step_info,
        catalog_name  type cl_apj_rt_api=>ty_catalog_name,
        template_name type cl_apj_rt_api=>ty_template_name,
      end of ty_notif_api.

  ...

  method if_apj_rt_run~execute.

    " use notification API to send a notification to inform about job start
    if p_is_simulation = abap_false.
      me->get_notif_api(  ).
      if gs_notif_api-notif_api is bound.
        gs_notif_api-notif_api->if_apj_rt_job_notif_exit~notify_jt_start(
          is_job_info               = gs_notif_api-job_info
          iv_job_catalog_entry_name = gs_notif_api-catalog_name
          iv_job_template_name      = gs_notif_api-template_name ).
      endif.
    endif.
  ...
  endmethod.
  
  method get_notif_api.
    try.
        " use notification exit to send notification that the job started
        cl_apj_rt_api=>get_job_runtime_info(
          importing
            ev_catalog_name   = gs_notif_api-catalog_name
            ev_template_name  = gs_notif_api-template_name
            ev_jobname        = gs_notif_api-job_info-job_name
            ev_jobcount       = gs_notif_api-job_info-job_count
            ev_stepcount      = gs_notif_api-job_info-job_step ).
      catch cx_apj_rt.
        return.
    endtry.

    gs_notif_api-notif_api = new zcl_apj_book_reorder_notif(  ).
  endmethod.
  • I created a new type for simply having all data that the notification exit needs in one place
  • Right at the start of the method, I am fetching all my notification exit data via new method get_notif_api
  • In the new method I am calling the application job API (released) to get all application job info and I create a reference object to my notification exit
  • Once we have a reference to the notification exit and all data, we can call the “start” method to create a notification to user, as we coded above

And finally, we are done. Let's see this all in action.

 

I'll be showcasing only the new features here, meaning the notification that will get created when I run a new job and also the automatic running of my notification exit code when the job fails/is cancelled. So in fact we won't be seeing a success in this demo, but that's no different from my previous post, if you are curious 😊

3.1 New application job

In the Application Jobs (F1240) (PCE version), I am creating a new job with my new Application Job Template:

Mlauber_1-1780995188183.Png

The one interesting thing above is the Bookstore Id parameter, where I entered ‘002' intentionally, because this bookstore does not exist and will cause my job to raise an exception, as I shown the code above in 2.3.

3.2 Job run result (when failed)

Now, when the job starts, my user should get a new notification in the Fiori Launchpad and I did:

Mlauber_4-1780995556118.Png

Remember we added the “navigation target” on this notification? Even though this makes no sense for this case, if I now click on this notification I will navigate to the app I defined:

Mlauber_5-1780995798161.Png

Secondly, because the job failed because the bookstore id does not exist, I should also get an email that informs me about the job failure due to the notification exit being called automatically when that happens; and yes, I did:

Mlauber_2-1780995391298.Png

We can also see the job as failed in the Fiori app itself:

Mlauber_3-1780995456445.Png

You see above I have in fact 2 logs: that is because the SAP Standard Application Job framework will always create a log automatically if a job execution fails. So even if I had forgotten to write the error into my own log, we would still have it. Here a look at both logs:

Mlauber_6-1780995885004.PngMlauber_7-1780995902789.Png

And voilà, that's a custom application job, updated to 2025 release version, with notification integration all done.

Hope this was helpful. Let me know if you wonder anything regarding this.

Source link

Leave a Reply

Your email address will not be published. Required fields are marked *