You're running a year-end reconciliation report that loops through 80,000 vendor invoices, validates them against GL postings, and stamps each record with an approval status. In a test system with 500 entries it finishes in under a minute. In production it runs for four hours — and the business is calling.
The knee-jerk fix most developers reach for is raw asynchronous RFC (aRFC): sprinkle CALL FUNCTION … STARTING NEW TASK … DESTINATION IN GROUP, wire up callback handlers, manage semaphore flags, and pray that your RFC server group is configured correctly across all application servers. It works — eventually — but the resulting code is fragile, hard to test, and nearly impossible for the next developer to maintain without a war story attached.
There is a better way: CL_ABAP_PARALLEL.
CL_ABAP_PARALLEL is a standard SAP class (available from SAP Basis 7.54 and fully released in ABAP Cloud) that wraps the complexity of aRFC-based parallel dispatch behind a clean, object-oriented interface. Instead of orchestrating raw function module calls, RFC groups, and callback patterns yourself, you define what each unit of work should do, hand the instances over to the framework, and let it handle the rest — process allocation, dispatching, timeout management, and result collection.
Under the hood, the framework still uses RS_ABAP_PARALLEL and the server dispatch class CL_SSI_DISPATCH, so the performance characteristics are identical to hand-rolled aRFC. The difference is in developer experience and maintainability.
SAP provides two distinct implementation strategies. Pick the one that fits your team's style and the complexity of your data contracts.
IF_ABAP_PARALLEL)This is the recommended modern approach, particularly for ABAP Cloud and clean-core projects. Your processing class implements the IF_ABAP_PARALLEL interface and carries its own typed input data as instance attributes. No serialization gymnastics required.
"---------------------------------------------------------------------
" Step 1: Define the processing task class
"---------------------------------------------------------------------
CLASS zcl_invoice_validation_task DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES if_abap_parallel.
INTERFACES if_serializable_object.
TYPES: BEGIN OF ty_result,
invoice_id TYPE vbeln,
is_valid TYPE abap_bool,
error_msg TYPE string,
END OF ty_result.
METHODS constructor
IMPORTING
is_invoice TYPE rbkp. " MIRO invoice header
METHODS get_result
RETURNING VALUE(rs_result) TYPE ty_result.
PRIVATE SECTION.
DATA ms_invoice TYPE rbkp.
DATA ms_result TYPE ty_result.
ENDCLASS.
CLASS zcl_invoice_validation_task IMPLEMENTATION.
METHOD constructor.
ms_invoice = is_invoice.
ENDMETHOD.
METHOD if_abap_parallel~do.
" All validation logic lives here — runs in a separate dialog WP
ms_result-invoice_id = ms_invoice-belnr.
" Example: Cross-check the invoice amount against the PO tolerance
DATA(lv_tolerance) = CONV dec15_5( '0.05' ). " 5 % tolerance
DATA lv_po_net TYPE wrbtr.
SELECT SINGLE netwr INTO lv_po_net
FROM ekko
WHERE ebeln = ms_invoice-xblnr. " vendor doc nr maps to PO
IF lv_po_net IS NOT INITIAL.
DATA(lv_deviation) = abs( ms_invoice-rwbtr - lv_po_net ) / lv_po_net.
IF lv_deviation <= lv_tolerance.
ms_result-is_valid = abap_true.
ELSE.
ms_result-is_valid = abap_false.
ms_result-error_msg = |Deviation { lv_deviation * 100 } % exceeds tolerance|.
ENDIF.
ELSE.
ms_result-is_valid = abap_false.
ms_result-error_msg = 'No matching purchase order found'.
ENDIF.
ENDMETHOD.
METHOD get_result.
rs_result = ms_result.
ENDMETHOD.
ENDCLASS.
"---------------------------------------------------------------------
" Step 2: Orchestrate the parallel run from your main program / method
"---------------------------------------------------------------------
CLASS zcl_invoice_processor DEFINITION
PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
CLASS-METHODS run_validation.
ENDCLASS.
CLASS zcl_invoice_processor IMPLEMENTATION.
METHOD run_validation.
" Fetch the raw invoice headers to validate
DATA lt_invoices TYPE TABLE OF rbkp.
SELECT * FROM rbkp
INTO TABLE @LT_invoices
WHERE bukrs = '1000'
AND bldat >= @( cl_abap_context_info=>get_system_date( ) - 30 ).
" Build one task object per invoice
DATA lt_tasks TYPE cl_abap_parallel=>t_in_inst_tab.
LOOP AT lt_invoices ASSIGNING FIELD-SYMBOL(<invoice>).
DATA(lo_task) = NEW zcl_invoice_validation_task( <invoice> ).
APPEND lo_task TO lt_tasks.
ENDLOOP.
" Instantiate the framework — use 40 % of available dialog processes
DATA(lo_parallel) = NEW cl_abap_parallel(
p_percentage = 40
p_timeout = 300 " seconds per task
).
" Fire!
DATA lt_results TYPE cl_abap_parallel=>t_out_inst_tab.
lo_parallel->run_inst(
EXPORTING p_in_tab = lt_tasks
IMPORTING p_out_tab = lt_results
).
" Harvest results
LOOP AT lt_results ASSIGNING FIELD-SYMBOL(<res>).
" Check for framework-level errors first (timeouts, system failures)
IF <res>-message IS NOT INITIAL.
MESSAGE <res>-message TYPE 'W'.
CONTINUE.
ENDIF.
" Cast the generic reference back to our concrete task class
DATA(lo_finished_task) = CAST zcl_invoice_validation_task( <res>-instance ).
DATA(ls_val_result) = lo_finished_task->get_result( ).
IF ls_val_result-is_valid = abap_false.
" Write rejection log, trigger workflow, etc.
cl_demo_output=>write( |Invoice { ls_val_result-invoice_id }: { ls_val_result-error_msg }| ).
ENDIF.
ENDLOOP.
ENDMETHOD.
ENDCLASS.The key advantage here is type safety. Your invoice data lives in a properly typed class attribute (ms_invoice TYPE rbkp) — no intermediate XSTRING packing or XML transformation. When you cast <res>-instance back to zcl_invoice_validation_task, you get full IntelliSense and compile-time checks in ADT.
CL_ABAP_PARALLEL (Classic Style)In this variant your task class inherits from CL_ABAP_PARALLEL and redefines the DO method. Input and output are exchanged via the inherited P_IN / P_OUT XSTRING parameters, which you serialize and deserialize yourself using EXPORT … TO DATA BUFFER / IMPORT … FROM DATA BUFFER.
This pattern is occasionally useful when working on older Basis releases or when you need to carry a heterogeneous shared payload (P_IN_ALL) across all tasks without repeating it for every record.
CLASS zcl_material_enrichment DEFINITION
PUBLIC INHERITING FROM cl_abap_parallel
FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS do REDEFINITION.
ENDCLASS.
CLASS zcl_material_enrichment IMPLEMENTATION.
METHOD do.
" Deserialize per-task input
TYPES: BEGIN OF ty_input,
matnr TYPE matnr,
werks TYPE werks_d,
END OF ty_input.
DATA ls_input TYPE ty_input.
IMPORT task_data = ls_input FROM DATA BUFFER p_in.
" Do the heavy lifting — e.g. call an external pricing API
DATA lv_price TYPE p LENGTH 10 DECIMALS 2.
" ... your logic here ...
" Serialize the result back
TYPES: BEGIN OF ty_output,
matnr TYPE matnr,
price TYPE p LENGTH 10 DECIMALS 2,
END OF ty_output.
DATA ls_output TYPE ty_output.
ls_output-matnr = ls_input-matnr.
ls_output-price = lv_price.
EXPORT result_data = ls_output TO DATA BUFFER p_out.
ENDMETHOD.
ENDCLASS.Practical tip: Prefer Pattern 1 (interface) for all new development. Reserve Pattern 2 for legacy codebases where you need XSTRING-based data exchange with an existing framework or where shared read-only context (P_IN_ALL) can meaningfully reduce payload size.
One of the most common sources of confusion with CL_ABAP_PARALLEL is the interplay between its four resource-control parameters. Here is a clear breakdown:
P_PERCENTAGE | I (0–100) | Use X % of the dialog processes that the system deems available at startup. Takes precedence over P_NUM_TASKS if both are supplied. |
P_NUM_TASKS | I | Request a fixed number of parallel slots. Ignored if P_PERCENTAGE is also set or if the value exceeds the system maximum. |
P_NUM_PROCESSES | I | Hard cap on the total simultaneous processes across all app servers. A safeguard against monopolizing the system. |
P_TIMEOUT | I (seconds) | Maximum wall-clock time a single task may run before the framework marks it as failed and populates <res>-message. |
P_LOCAL_SERVER | Flag | When set, all tasks run on the same application server as the calling program. Useful for debugging; avoid in production. |
Every entry in p_out_tab (or p_out_inst_tab) has a MESSAGE field. This is not optional to check. If a task times out, encounters a system failure, or the framework cannot dispatch it, the result record will have a non-initial MESSAGE and a blank result. Silently skipping that check means silently dropping data.
LOOP AT lt_results ASSIGNING FIELD-SYMBOL(<r>).
IF <r>-message IS NOT INITIAL.
" Log to application log (SLG1) instead of just writing to screen
cl_bali_log=>... " or your preferred logging utility
CONTINUE.
ENDIF.
" ... process normal result ...
ENDLOOP.Additionally, wrap your entire run_inst call in a TRY … CATCH block for cx_sy_parallel_processing to handle catastrophic dispatch failures gracefully.
| Boilerplate | RFC function module + callback handler + semaphore flags | One class implementing IF_ABAP_PARALLEL |
| Type safety | EXPORT/IMPORT tables, generic | Typed class attributes (Pattern 1) |
| Error handling | Manual SYSTEM-EXCEPTIONS per call | Built-in MESSAGE field per result |
| Testability | Difficult (requires RFC infrastructure) | Standard class — full unit-test coverage possible |
| ABAP Cloud compatible | No (aRFC not available in Cloud) | Yes (CL_ABAP_PARALLEL is released) |
CL_ABAP_PARALLEL does not do anything fundamentally new — parallel dialog dispatch has been in ABAP for decades. What it does is remove all the ceremony that made developers avoid it. With the interface-based pattern, adding parallelism to an existing data-processing class is often a matter of implementing one interface, one constructor, and adjusting a handful of lines in the orchestrating method.
If your team has been avoiding parallel processing because the old aRFC boilerplate felt too risky or too expensive to maintain, it is time to revisit that decision. Your business users — and the developers who come after you — will thank you.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
| User | Count |
|---|---|
| 26 | |
| 26 | |
| 21 | |
| 21 | |
| 19 | |
| 14 | |
| 14 | |
| 14 | |
| 14 | |
| 10 |