This post covers end-to-end implementation of an Application Job using IF_APJ_RT_EXEC_OBJECT and IF_APJ_DT_EXEC_OBJECT interfaces, including both Single Value parameters and Select Option parameters.
What is an Application Job?
Application Jobs (APJ) are the modern replacement for classic SM36/SM37 background jobs in S/4HANA. They run via class-based execution and support proper parameter definition through the ADT (Eclipse) framework.
Step 1 — Create the Class
Your class must implement two interfaces:
IF_APJ_RT_EXEC_OBJECT— Runtime executionIF_APJ_DT_EXEC_OBJECT— Design-time parameter definition
abap
CLASS zcl_my_apj_job DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES:
if_oo_adt_classrun,
if_apj_rt_exec_object,
if_apj_dt_exec_object.
" Single value parameter constants
CONSTANTS: gv_date TYPE char8 VALUE 'SnapDate',
gv_lang TYPE char5 VALUE 'LANGUAGE',
gv_uom TYPE char5 VALUE 'UOM'.
" Select option parameter constants
CONSTANTS: gv_contracttype TYPE char10 VALUE 'SO_CON',
gv_compcode TYPE char10 VALUE 'SO_COMP',
gv_plant TYPE char10 VALUE 'SO_Plant'.
ENDCLASS.
Step 2 — GET_PARAMETERS : Single Values
abap
METHOD if_apj_dt_exec_object~get_parameters.
CLEAR: et_parameter_def, et_parameter_val.
CONSTANTS: lc_langu TYPE char5 VALUE 'SPRAS',
lc_date TYPE char4 VALUE 'DATS',
lc_uom TYPE char5 VALUE 'MEINS'.
et_parameter_def = VALUE #(
( selname = gv_date kind = if_apj_dt_exec_object=>parameter datatype = lc_date length = 20
param_text = 'Snapshot Date'
changeable_ind = abap_true )
( selname = gv_lang kind = if_apj_dt_exec_object=>parameter datatype = lc_langu length = 20
param_text = 'Language'
changeable_ind = abap_true )
( selname = gv_uom kind = if_apj_dt_exec_object=>parameter datatype = lc_uom length = 20
param_text = 'Unit of Measure'
changeable_ind = abap_true )
).
ENDMETHOD.
Step 3 — GET_PARAMETERS : Select Options
abap
METHOD if_apj_dt_exec_object~get_parameters.
CLEAR: et_parameter_def, et_parameter_val.
CONSTANTS: lc_char TYPE char8 VALUE 'CHAR30',
lc_lngth TYPE int2 VALUE 50.
et_parameter_def = VALUE #(
( selname = gv_contracttype kind = if_apj_dt_exec_object=>select_option datatype = lc_char length = lc_lngth component_type = 'TCTYP'
param_text = 'Contract Type'
changeable_ind = abap_true )
( selname = gv_compcode kind = if_apj_dt_exec_object=>select_option datatype = lc_char length = lc_lngth component_type = 'BUKRS'
param_text = 'Company Code'
changeable_ind = abap_true )
( selname = gv_plant kind = if_apj_dt_exec_object=>select_option datatype = lc_char length = lc_lngth component_type = 'WERKS_D'
param_text = 'Plant'
changeable_ind = abap_true )
).
ENDMETHOD.
Step 4 — EXECUTE Method : Single Values
METHOD if_apj_rt_exec_object~execute.
DATA: lv_date TYPE dats,
lv_lang TYPE spras,
lv_uom TYPE meins,
lv_text_item TYPE string,
lv_string TYPE string,
lv_timestamp TYPE timestamp,
lv_filename TYPE string,
lt_final_data TYPE TABLE OF string.
" Read incoming parameters
LOOP AT it_parameters INTO DATA(ls_param) WHERE low IS NOT INITIAL.
CASE ls_param-selname.
WHEN gv_date. lv_date = ls_param-low.
WHEN gv_lang. lv_lang = ls_param-low.
WHEN gv_uom. lv_uom = ls_param-low.
ENDCASE.
ENDLOOP.
" Read from your CDS / DB view
SELECT * FROM zcds_your_query_view(
p_date = @lv_date,
p_language = @lv_lang,
p_uom = @lv_uom )
INTO TABLE @DATA(lt_results).
" Build pipe-delimited output dynamically using RTTI
LOOP AT lt_results INTO DATA(ls_row).
CLEAR lv_text_item.
DATA(lo_struct) = CAST cl_abap_structdescr(
cl_abap_typedescr=>describe_by_data( ls_row ) ).
LOOP AT lo_struct->components INTO DATA(ls_comp).
ASSIGN COMPONENT ls_comp-name OF STRUCTURE ls_row TO FIELD-SYMBOL().
IF IS ASSIGNED.
lv_string = CONV #( ).
IF lv_text_item IS INITIAL.
lv_text_item = lv_string.
ELSE.
CONCATENATE lv_text_item lv_string INTO lv_text_item SEPARATED BY '|'.
ENDIF.
ENDIF.
ENDLOOP.
APPEND lv_text_item TO lt_final_data.
ENDLOOP.
" Write output file
GET TIME STAMP FIELD lv_timestamp.
lv_filename = |ZREPORT_OUTPUT_{ lv_date }_{ lv_timestamp }|.
IF lt_final_data IS NOT INITIAL.
OPEN DATASET lv_filename FOR OUTPUT IN TEXT MODE ENCODING DEFAULT.
LOOP AT lt_final_data INTO DATA(ls_line).
TRANSFER ls_line TO lv_filename.
ENDLOOP.
CLOSE DATASET lv_filename.
ENDIF.
ENDMETHOD.
Step 5 — EXECUTE Method : Select Options
METHOD if_apj_rt_exec_object~execute.
DATA: lt_contract_type TYPE RANGE OF tctyp,
lt_plant TYPE RANGE OF werks_d,
lt_company_code TYPE RANGE OF bukrs,
lv_where_clause TYPE string,
lv_text_header TYPE string,
lv_text_item TYPE string,
lv_filename TYPE string,
lt_final_data TYPE TABLE OF string,
lt_temp TYPE dfies_tab.
" Build WHERE clause dynamically from select options
LOOP AT it_parameters INTO DATA(ls_param) WHERE low IS NOT INITIAL.
CASE ls_param-selname.
WHEN gv_contracttype.
APPEND VALUE #( sign = 'I'
option = ls_param-option low = ls_param-low high = ls_param-high ) TO lt_contract_type.
lv_where_clause = COND #(
WHEN lv_where_clause IS INITIAL
THEN 'contracttype IN @lt_contract_type'
ELSE lv_where_clause && ' AND contracttype IN @lt_contract_type' ).
WHEN gv_compcode.
APPEND VALUE #( sign = 'I'
option = ls_param-option low = ls_param-low high = ls_param-high ) TO lt_company_code.
lv_where_clause = COND #(
WHEN lv_where_clause IS INITIAL
THEN 'companycode IN @lt_company_code'
ELSE lv_where_clause && ' AND companycode IN @lt_company_code' ).
WHEN gv_plant.
APPEND VALUE #( sign = 'I'
option = ls_param-option low = ls_param-low high = ls_param-high ) TO lt_plant.
lv_where_clause = COND #(
WHEN lv_where_clause IS INITIAL
THEN 'plant IN @lt_plant'
ELSE lv_where_clause && ' AND plant IN @lt_plant' ).
ENDCASE.
ENDLOOP.
" Select using dynamic WHERE clause
SELECT * FROM zcds_your_position_view WHERE (lv_where_clause)
INTO TABLE @DATA(lt_position).
IF sy-subrc = 0.
" Get column headers via DDIF_FIELDINFO_GET
CALL FUNCTION 'DDIF_FIELDINFO_GET'
EXPORTING
tabname = 'ZCDS_YOUR_POSITION_VIEW'
langu = sy-langu TABLES
dfies_tab = lt_temp EXCEPTIONS
not_found = 1
internal_error = 2
OTHERS = 3.
" Build header row
DATA(lt_columns) = VALUE string_table(
FOR ls_f IN lt_temp INDEX INTO lv_idx WHERE ( lv_idx > 1 ) " skip MANDT
( ls_f-fieldname ) ).
LOOP AT lt_columns INTO DATA(lv_col).
lv_text_header = COND #(
WHEN lv_text_header IS INITIAL THEN lv_col ELSE lv_text_header && '|' && lv_col ).
ENDLOOP.
" Build data rows
LOOP AT lt_position INTO DATA(ls_pos).
" Concatenate your fields with | separator
CONCATENATE
ls_pos-field1 ls_pos-field2 ls_pos-field3 " ... add all required fields
INTO lv_text_item SEPARATED BY '|'.
APPEND lv_text_item TO lt_final_data.
ENDLOOP.
" Insert header at top
INSERT lv_text_header INTO lt_final_data INDEX 1.
" Write to application server
CONCATENATE '/usr/sap/trans/' sy-uname '_' sy-uzeit '.xls'
INTO lv_filename.
IF lt_final_data IS NOT INITIAL.
OPEN DATASET lv_filename FOR OUTPUT IN TEXT MODE ENCODING DEFAULT.
LOOP AT lt_final_data INTO DATA(ls_line).
TRANSFER ls_line TO lv_filename.
ENDLOOP.
CLOSE DATASET lv_filename.
ENDIF.
ENDIF.
ENDMETHOD.
Step 6 — Create Job Catalog & Template in ADT
- In ADT (Eclipse) → New → Application Job Catalog Entry
- Enter your class name (
ZCL_MY_APJ_JOB) - Give it a Catalog Name (e.g.
ZCAT_MY_JOB)
- Enter your class name (
- New → Application Job Template
- Reference the Catalog Name created above
- Define default parameter values
- Schedule via Application Jobs Fiori app (transaction
/n/SDF/JOBDor Fiori Launchpad)



