Hi RAP Developers, Good Day
Introduction:
In many business scenarios, we need to expose BAPI-based data to SAP Fiori applications without creating database tables or CDS-based persistency. With the RESTful ABAP Programming Model (RAP), this can be achieved efficiently using Custom Entities, which allow us to call ABAP logic directly—such as BAPIs—and return the data as an OData service.
In this blog, I will walk through how to build a Custom Entity that fetches bank details by calling a BAPI, how to annotate it for UI consumption, and how to expose it seamlessly to a Fiori Elements application. This approach is useful whenever you want to reuse existing BAPIs and business logic while keeping your RAP model clean, lightweight, and high-performing.
Solution Overview:
This solution demonstrates how RAP can seamlessly integrate with classic SAP functionality by exposing BAPI output through a Custom Entity. Instead of relying on database tables or CDS views, the Custom Entity acts as a virtual data source whose data is supplied dynamically via an ABAP class.
When a Fiori Elements UI requests the entity, RAP forwards the query to the ABAP implementation class. This class reads the query parameters (filters, sorting, pagination) and uses them to call BAPI_BANK_GETDETAIL. The BAPI returns the bank’s address, master data, and related details, which are then transformed into the response structure expected by the Custom Entity.
The result is an OData-based, read-only endpoint capable of delivering real-time data from a BAPI with full RAP features such as filtering, sorting, paging, and total count support. This approach makes it easy to modernize existing SAP data retrieval logic while still leveraging stable, proven BAPIs in the backend.
Custom Entity:
custom entity not underlying any data source.data preview is not possible, becuase this should be implemented in seperate class.suitable for paging,calling bapi’s and external api’s.@EndUserText.label: ‘Custom Entity calling bapi’
@ObjectModel: {
query: {
implementedBy: ‘ABAP:ZCL_BAPI_BANK’
}
}
@UI: {
headerInfo: {
typeName: ‘Banks’,
typeNamePlural: ‘Banks’,
title: { value: ‘bank_ctry’ },
description: { value: ‘bank_ctry’ }
}
}
@UI: {
presentationVariant: [{
sortOrder: [{
by: ‘bank_ctry’,
direction: #ASC
}],
visualizations: [{
type: #AS_LINEITEM
}]
}]
}
define root custom entity ZCE_BANK
{
.facet : [
{
id : ‘Bank_data’,
purpose : #STANDARD,
type : #IDENTIFICATION_REFERENCE,
label : ‘Bank Details’,
position: 10 }
]
.lineItem : [{ position: 10 }]
.selectionField : [{position: 10}]
.identification : [{position: 10}]
key bank_ctry : banks;
.lineItem : [{ position: 20 }]
.selectionField : [{position: 20}]
.identification : [{position: 20}]
key bank_key : bankk;
.lineItem : [{ position: 30 }]
.identification : [{position: 30}]
bank_name : banka;
.lineItem : [{ position: 40 }]
.identification : [{position: 40}]
street : stras_gp;
.lineItem : [{ position: 50 }]
.identification : [{position: 50}]
city : ort01_gp;
.lineItem : [{ position: 60 }]
.identification : [{position: 60}]
BANK_NO : bankl;
.lineItem : [{ position: 70 }]
.identification : [{position: 70}]
CREAT_DATE : erdat_bf;
.lineItem : [{ position: 80 }]
.identification : [{position: 80}]
creator : ernam_bf;
}
Implementation:
CLASS ZCL_BAPI_BANK DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
” RAP interface for query implementation
INTERFACES IF_RAP_QUERY_PROVIDER.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS ZCL_BAPI_BANK IMPLEMENTATION.
METHOD IF_RAP_QUERY_PROVIDER~SELECT.
“———————————————————————-
” Local type used to return data back to Custom Entity ZCE_BANK
“———————————————————————-
TYPES: BEGIN OF TY_BANK_RESULT,
BANK_CTRY TYPE BANKS,
BANK_KEY TYPE BANKK,
BANK_NAME TYPE BANKA,
STREET TYPE STRAS_GP,
CITY TYPE ORT01_GP,
BANK_NO TYPE BANKL,
CREAT_DATE TYPE ERDAT_BF,
CREATOR TYPE ERNAM_BF,
END OF TY_BANK_RESULT.
DATA: LT_RESULT TYPE TABLE OF TY_BANK_RESULT,
LS_RESULT TYPE TY_BANK_RESULT,
LS_ADDRESS TYPE BAPI1011_ADDRESS,
LS_DETAIL TYPE BAPI1011_DETAIL,
LS_RETURN TYPE BAPIRET2.
” Proceed only if the consumer has requested data
IF IO_REQUEST->IS_DATA_REQUESTED( ).
“—————————————————————
” Read pagination details (TOP and SKIP)
“—————————————————————
DATA(LV_TOP) = IO_REQUEST->GET_PAGING( )->GET_PAGE_SIZE( ).
DATA(LV_SKIP) = IO_REQUEST->GET_PAGING( )->GET_OFFSET( ).
“—————————————————————
” Prepare ORDER BY clause based on sort request from UI
“—————————————————————
DATA(LT_SORT) = IO_REQUEST->GET_SORT_ELEMENTS( ).
DATA LV_ORDERBY TYPE STRING.
IF LT_SORT IS NOT INITIAL.
CLEAR LV_ORDERBY.
LOOP AT LT_SORT INTO DATA(LS_SORT).
IF SY-TABIX > 1.
LV_ORDERBY = |{ LV_ORDERBY }, |.
ENDIF.
” Construct ORDER BY element (ASCENDING/DESCENDING)
LV_ORDERBY =
|{ LV_ORDERBY }{ LS_SORT-ELEMENT_NAME } {
COND #( WHEN LS_SORT-DESCENDING = ABAP_TRUE
THEN ‘DESCENDING’
ELSE ‘ASCENDING’ ) }|.
ENDLOOP.
ENDIF.
“—————————————————————
” Derive SQL filter string from UI filter (if any)
“—————————————————————
DATA(lv_conditions) = io_request->get_filter( )->get_as_sql_string( ).
“—————————————————————
” Read bank master records (BNKA) based on filters
“—————————————————————
SELECT BANKS AS bank_ctry,
BANKL AS bank_key,
BNKLZ AS bank_no,
ERDAT AS creat_date,
ERNAM AS creator
FROM BNKA
WHERE (lv_conditions)
ORDER BY (lv_orderby)
INTO TABLE @DATA(lt_banks)
UP TO @LV_top ROWS
OFFSET @LV_skip.
“—————————————————————
” Loop through each BNKA record and enrich details using BAPI
“—————————————————————
LOOP AT lt_banks INTO DATA(ls_bank).
” Get detailed bank address info
CALL FUNCTION ‘BAPI_BANK_GETDETAIL’
EXPORTING
bankcountry = ls_bank-bank_ctry
bankkey = ls_bank-bank_key
IMPORTING
bank_address = ls_address
bank_detail = ls_detail
return = ls_return.
” Prepare output row
LS_RESULT-bank_ctry = ls_bank-bank_ctry.
LS_RESULT-bank_key = ls_bank-bank_key.
LS_RESULT-bank_name = ls_address-bank_name.
LS_RESULT-street = ls_address-street.
LS_RESULT-city = ls_address-city.
LS_RESULT-bank_no = ls_bank-bank_no.
LS_RESULT-creat_date = ls_bank-creat_date.
LS_RESULT-creator = ls_bank-creator.
APPEND LS_RESULT TO LT_RESULT.
CLEAR: ls_result, ls_address, ls_detail, ls_return.
ENDLOOP.
“—————————————————————
” Return total count for UI pagination (if requested)
“—————————————————————
IF IO_REQUEST->IS_TOTAL_NUMB_OF_REC_REQUESTED( ).
SELECT COUNT(*)
FROM BNKA
WHERE (lv_conditions)
INTO @DATA(lv_count).
IO_RESPONSE->SET_TOTAL_NUMBER_OF_RECORDS( lv_count ).
ENDIF.
“—————————————————————
” Send final result back to RAP framework
“—————————————————————
IO_RESPONSE->SET_DATA( LT_RESULT ).
ENDIF.
ENDMETHOD.
ENDCLASS.
Conclusion:
this approach highlights how RAP can modernize SAP applications by exposing classic BAPI data through Custom Entities. It enables real-time, read-only access with full OData capabilities—filtering, sorting, paging—without changing existing backend logic, bridging the gap between proven SAP functionality and modern Fiori UIs.
Thanks For Reading, Best Wishes
Hi RAP Developers, Good DayIntroduction:In many business scenarios, we need to expose BAPI-based data to SAP Fiori applications without creating database tables or CDS-based persistency. With the RESTful ABAP Programming Model (RAP), this can be achieved efficiently using Custom Entities, which allow us to call ABAP logic directly—such as BAPIs—and return the data as an OData service.In this blog, I will walk through how to build a Custom Entity that fetches bank details by calling a BAPI, how to annotate it for UI consumption, and how to expose it seamlessly to a Fiori Elements application. This approach is useful whenever you want to reuse existing BAPIs and business logic while keeping your RAP model clean, lightweight, and high-performing.Solution Overview:This solution demonstrates how RAP can seamlessly integrate with classic SAP functionality by exposing BAPI output through a Custom Entity. Instead of relying on database tables or CDS views, the Custom Entity acts as a virtual data source whose data is supplied dynamically via an ABAP class.When a Fiori Elements UI requests the entity, RAP forwards the query to the ABAP implementation class. This class reads the query parameters (filters, sorting, pagination) and uses them to call BAPI_BANK_GETDETAIL. The BAPI returns the bank’s address, master data, and related details, which are then transformed into the response structure expected by the Custom Entity.The result is an OData-based, read-only endpoint capable of delivering real-time data from a BAPI with full RAP features such as filtering, sorting, paging, and total count support. This approach makes it easy to modernize existing SAP data retrieval logic while still leveraging stable, proven BAPIs in the backend.Custom Entity:custom entity not underlying any data source.data preview is not possible, becuase this should be implemented in seperate class.suitable for paging,calling bapi’s and external api’s.@EndUserText.label: ‘Custom Entity calling bapi’
@ObjectModel: {
query: {
implementedBy: ‘ABAP:ZCL_BAPI_BANK’
}
}
@UI: {
headerInfo: {
typeName: ‘Banks’,
typeNamePlural: ‘Banks’,
title: { value: ‘bank_ctry’ },
description: { value: ‘bank_ctry’ }
}
}
@UI: {
presentationVariant: [{
sortOrder: [{
by: ‘bank_ctry’,
direction: #ASC
}],
visualizations: [{
type: #AS_LINEITEM
}]
}]
}
define root custom entity ZCE_BANK
{
.facet : [
{
id : ‘Bank_data’,
purpose : #STANDARD,
type : #IDENTIFICATION_REFERENCE,
label : ‘Bank Details’,
position: 10 }
]
.lineItem : [{ position: 10 }]
.selectionField : [{position: 10}]
.identification : [{position: 10}]
key bank_ctry : banks;
.lineItem : [{ position: 20 }]
.selectionField : [{position: 20}]
.identification : [{position: 20}]
key bank_key : bankk;
.lineItem : [{ position: 30 }]
.identification : [{position: 30}]
bank_name : banka;
.lineItem : [{ position: 40 }]
.identification : [{position: 40}]
street : stras_gp;
.lineItem : [{ position: 50 }]
.identification : [{position: 50}]
city : ort01_gp;
.lineItem : [{ position: 60 }]
.identification : [{position: 60}]
BANK_NO : bankl;
.lineItem : [{ position: 70 }]
.identification : [{position: 70}]
CREAT_DATE : erdat_bf;
.lineItem : [{ position: 80 }]
.identification : [{position: 80}]
creator : ernam_bf;
}Implementation:CLASS ZCL_BAPI_BANK DEFINITION
PUBLIC
FINAL
CREATE PUBLIC .
PUBLIC SECTION.
” RAP interface for query implementation
INTERFACES IF_RAP_QUERY_PROVIDER.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.
CLASS ZCL_BAPI_BANK IMPLEMENTATION.
METHOD IF_RAP_QUERY_PROVIDER~SELECT.
“———————————————————————-
” Local type used to return data back to Custom Entity ZCE_BANK
“———————————————————————-
TYPES: BEGIN OF TY_BANK_RESULT,
BANK_CTRY TYPE BANKS,
BANK_KEY TYPE BANKK,
BANK_NAME TYPE BANKA,
STREET TYPE STRAS_GP,
CITY TYPE ORT01_GP,
BANK_NO TYPE BANKL,
CREAT_DATE TYPE ERDAT_BF,
CREATOR TYPE ERNAM_BF,
END OF TY_BANK_RESULT.
DATA: LT_RESULT TYPE TABLE OF TY_BANK_RESULT,
LS_RESULT TYPE TY_BANK_RESULT,
LS_ADDRESS TYPE BAPI1011_ADDRESS,
LS_DETAIL TYPE BAPI1011_DETAIL,
LS_RETURN TYPE BAPIRET2.
” Proceed only if the consumer has requested data
IF IO_REQUEST->IS_DATA_REQUESTED( ).
“—————————————————————
” Read pagination details (TOP and SKIP)
“—————————————————————
DATA(LV_TOP) = IO_REQUEST->GET_PAGING( )->GET_PAGE_SIZE( ).
DATA(LV_SKIP) = IO_REQUEST->GET_PAGING( )->GET_OFFSET( ).
“—————————————————————
” Prepare ORDER BY clause based on sort request from UI
“—————————————————————
DATA(LT_SORT) = IO_REQUEST->GET_SORT_ELEMENTS( ).
DATA LV_ORDERBY TYPE STRING.
IF LT_SORT IS NOT INITIAL.
CLEAR LV_ORDERBY.
LOOP AT LT_SORT INTO DATA(LS_SORT).
IF SY-TABIX > 1.
LV_ORDERBY = |{ LV_ORDERBY }, |.
ENDIF.
” Construct ORDER BY element (ASCENDING/DESCENDING)
LV_ORDERBY =
|{ LV_ORDERBY }{ LS_SORT-ELEMENT_NAME } {
COND #( WHEN LS_SORT-DESCENDING = ABAP_TRUE
THEN ‘DESCENDING’
ELSE ‘ASCENDING’ ) }|.
ENDLOOP.
ENDIF.
“—————————————————————
” Derive SQL filter string from UI filter (if any)
“—————————————————————
DATA(lv_conditions) = io_request->get_filter( )->get_as_sql_string( ).
“—————————————————————
” Read bank master records (BNKA) based on filters
“—————————————————————
SELECT BANKS AS bank_ctry,
BANKL AS bank_key,
BNKLZ AS bank_no,
ERDAT AS creat_date,
ERNAM AS creator
FROM BNKA
WHERE (lv_conditions)
ORDER BY (lv_orderby)
INTO TABLE @DATA(lt_banks)
UP TO @LV_top ROWS
OFFSET @LV_skip.
“—————————————————————
” Loop through each BNKA record and enrich details using BAPI
“—————————————————————
LOOP AT lt_banks INTO DATA(ls_bank).
” Get detailed bank address info
CALL FUNCTION ‘BAPI_BANK_GETDETAIL’
EXPORTING
bankcountry = ls_bank-bank_ctry
bankkey = ls_bank-bank_key
IMPORTING
bank_address = ls_address
bank_detail = ls_detail
return = ls_return.
” Prepare output row
LS_RESULT-bank_ctry = ls_bank-bank_ctry.
LS_RESULT-bank_key = ls_bank-bank_key.
LS_RESULT-bank_name = ls_address-bank_name.
LS_RESULT-street = ls_address-street.
LS_RESULT-city = ls_address-city.
LS_RESULT-bank_no = ls_bank-bank_no.
LS_RESULT-creat_date = ls_bank-creat_date.
LS_RESULT-creator = ls_bank-creator.
APPEND LS_RESULT TO LT_RESULT.
CLEAR: ls_result, ls_address, ls_detail, ls_return.
ENDLOOP.
“—————————————————————
” Return total count for UI pagination (if requested)
“—————————————————————
IF IO_REQUEST->IS_TOTAL_NUMB_OF_REC_REQUESTED( ).
SELECT COUNT(*)
FROM BNKA
WHERE (lv_conditions)
INTO @DATA(lv_count).
IO_RESPONSE->SET_TOTAL_NUMBER_OF_RECORDS( lv_count ).
ENDIF.
“—————————————————————
” Send final result back to RAP framework
“—————————————————————
IO_RESPONSE->SET_DATA( LT_RESULT ).
ENDIF.
ENDMETHOD.
ENDCLASS.Conclusion: this approach highlights how RAP can modernize SAP applications by exposing classic BAPI data through Custom Entities. It enables real-time, read-only access with full OData capabilities—filtering, sorting, paging—without changing existing backend logic, bridging the gap between proven SAP functionality and modern Fiori UIs.Thanks For Reading, Best Wishes Read More Technology Blog Posts by Members articles
#SAP
#SAPTechnologyblog